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: 2024-11-21 22:18:42 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       29304 : GDALDataset *NSIDCbinDataset::Open(GDALOpenInfo *poOpenInfo)
     222             : 
     223             : {
     224             : 
     225             :     // Confirm that the header is compatible with a NSIDC dataset.
     226       29304 :     if (!Identify(poOpenInfo))
     227       29272 :         return nullptr;
     228             : 
     229             :     // Confirm the requested access is supported.
     230          15 :     if (poOpenInfo->eAccess == GA_Update)
     231             :     {
     232           0 :         CPLError(
     233             :             CE_Failure, CPLE_NotSupported,
     234             :             "The NSIDCbin driver does not support update access to existing "
     235             :             "datasets.");
     236           0 :         return nullptr;
     237             :     }
     238             : 
     239             :     // Check that the file pointer from GDALOpenInfo* is available
     240          15 :     if (poOpenInfo->fpL == nullptr)
     241             :     {
     242           0 :         return nullptr;
     243             :     }
     244             : 
     245             :     /* -------------------------------------------------------------------- */
     246             :     /*      Create a corresponding GDALDataset.                             */
     247             :     /* -------------------------------------------------------------------- */
     248          16 :     auto poDS = std::make_unique<NSIDCbinDataset>();
     249             : 
     250           1 :     poDS->eAccess = poOpenInfo->eAccess;
     251           1 :     std::swap(poDS->fp, poOpenInfo->fpL);
     252             : 
     253             :     /* -------------------------------------------------------------------- */
     254             :     /*      Read the header information.                                    */
     255             :     /* -------------------------------------------------------------------- */
     256           1 :     if (VSIFReadL(&(poDS->sHeader), 300, 1, poDS->fp) != 1)
     257             :     {
     258           0 :         CPLError(CE_Failure, CPLE_FileIO,
     259             :                  "Attempt to read 300 byte header filed on file %s\n",
     260             :                  poOpenInfo->pszFilename);
     261           0 :         return nullptr;
     262             :     }
     263             : 
     264             :     // avoid unused warnings
     265           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.missing_int);
     266           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.internal1);
     267           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.latitude);
     268           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.greenwich);
     269           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.internal2);
     270           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.jpole);
     271           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.ipole);
     272           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.julian_start);
     273           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.hour_start);
     274           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.minute_start);
     275           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.julian_end);
     276           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.hour_end);
     277           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.minute_end);
     278           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.channel);
     279           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.scaling);
     280             : 
     281             :     /* -------------------------------------------------------------------- */
     282             :     /*      Extract information of interest from the header.                */
     283             :     /* -------------------------------------------------------------------- */
     284             : 
     285           1 :     poDS->nRasterXSize = atoi(poDS->sHeader.columns);
     286           1 :     poDS->nRasterYSize = atoi(poDS->sHeader.rows);
     287             : 
     288           1 :     const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     289           1 :     bool south = STARTS_WITH(psHeader + 230, "ANTARCTIC");
     290             : 
     291           1 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     292             :     {
     293           0 :         return nullptr;
     294             :     }
     295             : 
     296             :     /* -------------------------------------------------------------------- */
     297             :     /*      Extract metadata from the header.                               */
     298             :     /* -------------------------------------------------------------------- */
     299             : 
     300           1 :     poDS->SetMetadataItem("INSTRUMENT", poDS->sHeader.instrument);
     301           1 :     poDS->SetMetadataItem("YEAR", stripLeadingSpaces_nsidc(poDS->sHeader.year));
     302           2 :     poDS->SetMetadataItem("JULIAN_DAY",
     303           1 :                           stripLeadingSpaces_nsidc(poDS->sHeader.julian));
     304           2 :     poDS->SetMetadataItem(
     305             :         "DATA_DESCRIPTORS",
     306           1 :         stripLeadingSpaces_nsidc(poDS->sHeader.data_descriptors));
     307           1 :     poDS->SetMetadataItem("IMAGE_TITLE", poDS->sHeader.imagetitle);
     308           2 :     poDS->SetMetadataItem("FILENAME",
     309           1 :                           stripLeadingSpaces_nsidc(poDS->sHeader.filename));
     310           1 :     poDS->SetMetadataItem("DATA_INFORMATION", poDS->sHeader.data_information);
     311             : 
     312             :     /* -------------------------------------------------------------------- */
     313             :     /*      Create band information objects.                                */
     314             :     /* -------------------------------------------------------------------- */
     315           1 :     int nBytesPerSample = 1;
     316             : 
     317             :     auto poBand = std::make_unique<NSIDCbinRasterBand>(
     318           1 :         poDS.get(), 1, poDS->fp, 300, nBytesPerSample, poDS->nRasterXSize,
     319           3 :         GDT_Byte);
     320           1 :     if (!poBand->IsValid())
     321           0 :         return nullptr;
     322           1 :     poDS->SetBand(1, std::move(poBand));
     323             : 
     324             :     /* -------------------------------------------------------------------- */
     325             :     /*      Geotransform, we simply know this from the documentation.       */
     326             :     /*       If we have similar binary files (at 12.5km for example) then   */
     327             :     /*        need more nuanced handling                                    */
     328             :     /*      Projection,  this is not technically enough, because the old    */
     329             :     /*       stuff is Hughes 1980.                                          */
     330             :     /*      FIXME: old or new epsg codes based on header info, or jul/year  */
     331             :     /* -------------------------------------------------------------------- */
     332             : 
     333           1 :     int epsg = -1;
     334           1 :     if (south)
     335             :     {
     336           1 :         poDS->adfGeoTransform[0] = -3950000.0;
     337           1 :         poDS->adfGeoTransform[1] = 25000;
     338           1 :         poDS->adfGeoTransform[2] = 0.0;
     339           1 :         poDS->adfGeoTransform[3] = 4350000.0;
     340           1 :         poDS->adfGeoTransform[4] = 0.0;
     341           1 :         poDS->adfGeoTransform[5] = -25000;
     342             : 
     343           1 :         epsg = 3976;
     344             :     }
     345             :     else
     346             :     {
     347           0 :         poDS->adfGeoTransform[0] = -3837500;
     348           0 :         poDS->adfGeoTransform[1] = 25000;
     349           0 :         poDS->adfGeoTransform[2] = 0.0;
     350           0 :         poDS->adfGeoTransform[3] = 5837500;
     351           0 :         poDS->adfGeoTransform[4] = 0.0;
     352           0 :         poDS->adfGeoTransform[5] = -25000;
     353             : 
     354           0 :         epsg = 3413;
     355             :     }
     356             : 
     357           1 :     if (poDS->m_oSRS.importFromEPSG(epsg) != OGRERR_NONE)
     358             :     {
     359           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     360             :                  "Unknown error initializing SRS from ESPG code. ");
     361           0 :         return nullptr;
     362             :     }
     363             : 
     364             :     /* -------------------------------------------------------------------- */
     365             :     /*      Initialize any PAM information.                                 */
     366             :     /* -------------------------------------------------------------------- */
     367           1 :     poDS->SetDescription(poOpenInfo->pszFilename);
     368           1 :     poDS->TryLoadXML();
     369             : 
     370           1 :     return poDS.release();
     371             : }
     372             : 
     373             : /************************************************************************/
     374             : /*                              Identify()                              */
     375             : /************************************************************************/
     376       29299 : int NSIDCbinDataset::Identify(GDALOpenInfo *poOpenInfo)
     377             : {
     378             : 
     379             :     // -------------------------------------------------------------------- /
     380             :     //      Works for daily and monthly, north and south NSIDC binary files /
     381             :     //      north and south are different dimensions, different extents but /
     382             :     //      both are 25000m resolution.
     383             :     //
     384             :     //      First we check to see if the file has the expected header       /
     385             :     //      bytes.                                                          /
     386             :     // -------------------------------------------------------------------- /
     387             : 
     388       29299 :     if (poOpenInfo->nHeaderBytes < 300 || poOpenInfo->fpL == nullptr)
     389       27491 :         return FALSE;
     390             : 
     391        1808 :     const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     392             :     // Check if century values seem reasonable.
     393        1808 :     if (!(EQUALN(psHeader + 103, "20", 2) || EQUALN(psHeader + 103, "19", 2) ||
     394             :           // the first files 1978 don't have a space at the start
     395        1807 :           EQUALN(psHeader + 102, "20", 2) || EQUALN(psHeader + 102, "19", 2)))
     396             :     {
     397        1807 :         return FALSE;
     398             :     }
     399             : 
     400             :     // Check if descriptors reasonable.
     401           1 :     if (!(STARTS_WITH(psHeader + 230, "ANTARCTIC") ||
     402           0 :           STARTS_WITH(psHeader + 230, "ARCTIC")))
     403             :     {
     404             : 
     405           0 :         return FALSE;
     406             :     }
     407             : 
     408           1 :     return TRUE;
     409             : }
     410             : 
     411             : /************************************************************************/
     412             : /*                           GetSpatialRef()                            */
     413             : /************************************************************************/
     414             : 
     415           0 : const OGRSpatialReference *NSIDCbinDataset::GetSpatialRef() const
     416             : {
     417           0 :     return &m_oSRS;
     418             : }
     419             : 
     420             : /************************************************************************/
     421             : /*                          GetGeoTransform()                           */
     422             : /************************************************************************/
     423             : 
     424           0 : CPLErr NSIDCbinDataset::GetGeoTransform(double *padfTransform)
     425             : 
     426             : {
     427           0 :     memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
     428             : 
     429           0 :     return CE_None;
     430             : }
     431             : 
     432             : /************************************************************************/
     433             : /*                             GetUnitType()                            */
     434             : /************************************************************************/
     435             : 
     436           0 : const char *NSIDCbinRasterBand::GetUnitType()
     437             : {
     438             :     // undecided, atm stick with Byte but may switch to Float and lose values >
     439             :     // 250 or generalize to non-raw driver
     440             :     // https://lists.osgeo.org/pipermail/gdal-dev/2022-August/056144.html
     441             :     // if (eDataType == GDT_Float32)
     442             :     //     return "Percentage";  // or "Fraction [0,1]"
     443             : 
     444             :     // Byte values don't have a clear unit type
     445           0 :     return "";
     446             : }
     447             : 
     448             : /************************************************************************/
     449             : /*                          GDALRegister_NSIDCbin()                        */
     450             : /************************************************************************/
     451             : 
     452        1595 : void GDALRegister_NSIDCbin()
     453             : 
     454             : {
     455        1595 :     if (GDALGetDriverByName("NSIDCbin") != nullptr)
     456         302 :         return;
     457             : 
     458        1293 :     GDALDriver *poDriver = new GDALDriver();
     459             : 
     460        1293 :     poDriver->SetDescription("NSIDCbin");
     461        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     462        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     463        1293 :                               "NSIDC Sea Ice Concentrations binary (.bin)");
     464        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     465        1293 :                               "drivers/raster/nsidcbin.html");
     466        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     467        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "bin");
     468             : 
     469        1293 :     poDriver->pfnOpen = NSIDCbinDataset::Open;
     470             : 
     471        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     472             : }

Generated by: LCOV version 1.14