mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-08-25 14:22:54 -06:00
949 lines
35 KiB
C++
949 lines
35 KiB
C++
/******************************************************************************
|
|
* $Id: gribdataset.cpp 28459 2015-02-12 13:48:21Z rouault $
|
|
*
|
|
* Project: GRIB Driver
|
|
* Purpose: GDALDataset driver for GRIB translator for read support
|
|
* Author: Bas Retsios, retsios@itc.nl
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2007, ITC
|
|
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
******************************************************************************
|
|
*
|
|
*/
|
|
|
|
#include "gdal_pam.h"
|
|
#include "cpl_multiproc.h"
|
|
|
|
#include "degrib18/degrib/degrib2.h"
|
|
#include "degrib18/degrib/inventory.h"
|
|
#include "degrib18/degrib/myerror.h"
|
|
#include "degrib18/degrib/filedatasource.h"
|
|
#include "degrib18/degrib/memorydatasource.h"
|
|
|
|
#include "ogr_spatialref.h"
|
|
|
|
CPL_CVSID("$Id: gribdataset.cpp 28459 2015-02-12 13:48:21Z rouault $");
|
|
|
|
CPL_C_START
|
|
void GDALRegister_GRIB(void);
|
|
CPL_C_END
|
|
|
|
static CPLMutex *hGRIBMutex = NULL;
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* GRIBDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class GRIBRasterBand;
|
|
|
|
class GRIBDataset : public GDALPamDataset
|
|
{
|
|
friend class GRIBRasterBand;
|
|
|
|
public:
|
|
GRIBDataset();
|
|
~GRIBDataset();
|
|
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
static int Identify( GDALOpenInfo * );
|
|
|
|
CPLErr GetGeoTransform( double * padfTransform );
|
|
const char *GetProjectionRef();
|
|
|
|
private:
|
|
void SetGribMetaData(grib_MetaData* meta);
|
|
VSILFILE *fp;
|
|
char *pszProjection;
|
|
OGRCoordinateTransformation *poTransform;
|
|
double adfGeoTransform[6]; // Calculate and store once as GetGeoTransform may be called multiple times
|
|
|
|
GIntBig nCachedBytes;
|
|
GIntBig nCachedBytesThreshold;
|
|
int bCacheOnlyOneBand;
|
|
GRIBRasterBand* poLastUsedBand;
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* GRIBRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class GRIBRasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class GRIBDataset;
|
|
|
|
public:
|
|
GRIBRasterBand( GRIBDataset*, int, inventoryType* );
|
|
virtual ~GRIBRasterBand();
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
virtual const char *GetDescription() const;
|
|
|
|
virtual double GetNoDataValue( int *pbSuccess = NULL );
|
|
|
|
void FindPDSTemplate();
|
|
|
|
void UncacheData();
|
|
|
|
private:
|
|
|
|
CPLErr LoadData();
|
|
|
|
static void ReadGribData( DataSource &, sInt4, int, double**, grib_MetaData**);
|
|
sInt4 start;
|
|
int subgNum;
|
|
char *longFstLevel;
|
|
|
|
double * m_Grib_Data;
|
|
grib_MetaData* m_Grib_MetaData;
|
|
|
|
int nGribDataXSize;
|
|
int nGribDataYSize;
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ConvertUnitInText() */
|
|
/************************************************************************/
|
|
|
|
static CPLString ConvertUnitInText(int bMetricUnits, const char* pszTxt)
|
|
{
|
|
if( !bMetricUnits )
|
|
return pszTxt;
|
|
|
|
CPLString osRes(pszTxt);
|
|
size_t iPos = osRes.find("[K]");
|
|
if( iPos != std::string::npos )
|
|
osRes = osRes.substr(0, iPos) + "[C]" + osRes.substr(iPos + 3);
|
|
return osRes;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GRIBRasterBand() */
|
|
/************************************************************************/
|
|
|
|
GRIBRasterBand::GRIBRasterBand( GRIBDataset *poDS, int nBand,
|
|
inventoryType *psInv )
|
|
: m_Grib_Data(NULL), m_Grib_MetaData(NULL)
|
|
{
|
|
this->poDS = poDS;
|
|
this->nBand = nBand;
|
|
this->start = psInv->start;
|
|
this->subgNum = psInv->subgNum;
|
|
this->longFstLevel = CPLStrdup(psInv->longFstLevel);
|
|
|
|
eDataType = GDT_Float64; // let user do -ot Float32 if needed for saving space, GRIB contains Float64 (though not fully utilized most of the time)
|
|
|
|
nBlockXSize = poDS->nRasterXSize;
|
|
nBlockYSize = 1;
|
|
|
|
nGribDataXSize = poDS->nRasterXSize;
|
|
nGribDataYSize = poDS->nRasterYSize;
|
|
|
|
const char* pszGribNormalizeUnits = CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
|
|
int bMetricUnits = CSLTestBoolean(pszGribNormalizeUnits);
|
|
|
|
SetMetadataItem( "GRIB_UNIT", ConvertUnitInText(bMetricUnits, psInv->unitName) );
|
|
SetMetadataItem( "GRIB_COMMENT", ConvertUnitInText(bMetricUnits, psInv->comment) );
|
|
SetMetadataItem( "GRIB_ELEMENT", psInv->element );
|
|
SetMetadataItem( "GRIB_SHORT_NAME", psInv->shortFstLevel );
|
|
SetMetadataItem( "GRIB_REF_TIME",
|
|
CPLString().Printf("%12.0f sec UTC", psInv->refTime ) );
|
|
SetMetadataItem( "GRIB_VALID_TIME",
|
|
CPLString().Printf("%12.0f sec UTC", psInv->validTime ) );
|
|
SetMetadataItem( "GRIB_FORECAST_SECONDS",
|
|
CPLString().Printf("%.0f sec", psInv->foreSec ) );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindPDSTemplate() */
|
|
/* */
|
|
/* Scan the file for the PDS template info and represent it as */
|
|
/* metadata. */
|
|
/************************************************************************/
|
|
|
|
void GRIBRasterBand::FindPDSTemplate()
|
|
|
|
{
|
|
GRIBDataset *poGDS = (GRIBDataset *) poDS;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Collect section 4 octet information ... we read the file */
|
|
/* ourselves since the GRIB API does not appear to preserve all */
|
|
/* this for us. */
|
|
/* -------------------------------------------------------------------- */
|
|
GIntBig nOffset = VSIFTellL( poGDS->fp );
|
|
GByte abyHead[5];
|
|
GUInt32 nSectSize;
|
|
|
|
VSIFSeekL( poGDS->fp, start+16, SEEK_SET );
|
|
VSIFReadL( abyHead, 5, 1, poGDS->fp );
|
|
|
|
while( abyHead[4] != 4 )
|
|
{
|
|
memcpy( &nSectSize, abyHead, 4 );
|
|
CPL_MSBPTR32( &nSectSize );
|
|
|
|
if( VSIFSeekL( poGDS->fp, nSectSize-5, SEEK_CUR ) != 0
|
|
|| VSIFReadL( abyHead, 5, 1, poGDS->fp ) != 1 )
|
|
break;
|
|
}
|
|
|
|
if( abyHead[4] == 4 )
|
|
{
|
|
GUInt16 nCoordCount;
|
|
GUInt16 nPDTN;
|
|
CPLString osOctet;
|
|
int i;
|
|
GByte *pabyBody;
|
|
|
|
memcpy( &nSectSize, abyHead, 4 );
|
|
CPL_MSBPTR32( &nSectSize );
|
|
|
|
pabyBody = (GByte *) CPLMalloc(nSectSize-5);
|
|
VSIFReadL( pabyBody, 1, nSectSize-5, poGDS->fp );
|
|
|
|
memcpy( &nCoordCount, pabyBody + 5 - 5, 2 );
|
|
CPL_MSBPTR16( &nCoordCount );
|
|
|
|
memcpy( &nPDTN, pabyBody + 7 - 5, 2 );
|
|
CPL_MSBPTR16( &nPDTN );
|
|
|
|
SetMetadataItem( "GRIB_PDS_PDTN",
|
|
CPLString().Printf( "%d", nPDTN ) );
|
|
|
|
for( i = 9; i < (int) nSectSize; i++ )
|
|
{
|
|
char szByte[10];
|
|
|
|
if( i == 9 )
|
|
sprintf( szByte, "%d", pabyBody[i-5] );
|
|
else
|
|
sprintf( szByte, " %d", pabyBody[i-5] );
|
|
osOctet += szByte;
|
|
}
|
|
|
|
SetMetadataItem( "GRIB_PDS_TEMPLATE_NUMBERS", osOctet );
|
|
|
|
CPLFree( pabyBody );
|
|
}
|
|
|
|
VSIFSeekL( poGDS->fp, nOffset, SEEK_SET );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetDescription() */
|
|
/************************************************************************/
|
|
|
|
const char * GRIBRasterBand::GetDescription() const
|
|
{
|
|
if( longFstLevel == NULL )
|
|
return GDALPamRasterBand::GetDescription();
|
|
else
|
|
return longFstLevel;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* LoadData() */
|
|
/************************************************************************/
|
|
|
|
CPLErr GRIBRasterBand::LoadData()
|
|
|
|
{
|
|
if( !m_Grib_Data )
|
|
{
|
|
GRIBDataset *poGDS = (GRIBDataset *) poDS;
|
|
|
|
if (poGDS->bCacheOnlyOneBand)
|
|
{
|
|
/* In "one-band-at-a-time" strategy, if the last recently used */
|
|
/* band is not that one, uncache it. We could use a smarter strategy */
|
|
/* based on a LRU, but that's a bit overkill for now. */
|
|
poGDS->poLastUsedBand->UncacheData();
|
|
poGDS->nCachedBytes = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Once we have cached more than nCachedBytesThreshold bytes, we will switch */
|
|
/* to "one-band-at-a-time" strategy, instead of caching all bands that have */
|
|
/* been accessed */
|
|
if (poGDS->nCachedBytes > poGDS->nCachedBytesThreshold)
|
|
{
|
|
CPLDebug("GRIB", "Maximum band cache size reached for this dataset. "
|
|
"Caching only one band at a time from now");
|
|
for(int i=0;i<poGDS->nBands;i++)
|
|
{
|
|
((GRIBRasterBand*) poGDS->GetRasterBand(i+1))->UncacheData();
|
|
}
|
|
poGDS->nCachedBytes = 0;
|
|
poGDS->bCacheOnlyOneBand = TRUE;
|
|
}
|
|
}
|
|
|
|
FileDataSource grib_fp (poGDS->fp);
|
|
|
|
// we don't seem to have any way to detect errors in this!
|
|
ReadGribData(grib_fp, start, subgNum, &m_Grib_Data, &m_Grib_MetaData);
|
|
if( !m_Grib_Data )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined, "Out of memory." );
|
|
return CE_Failure;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check that this band matches the dataset as a whole, size */
|
|
/* wise. (#3246) */
|
|
/* -------------------------------------------------------------------- */
|
|
nGribDataXSize = m_Grib_MetaData->gds.Nx;
|
|
nGribDataYSize = m_Grib_MetaData->gds.Ny;
|
|
|
|
poGDS->nCachedBytes += nGribDataXSize * nGribDataYSize * sizeof(double);
|
|
poGDS->poLastUsedBand = this;
|
|
|
|
if( nGribDataXSize != nRasterXSize
|
|
|| nGribDataYSize != nRasterYSize )
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Band %d of GRIB dataset is %dx%d, while the first band and dataset is %dx%d. Georeferencing of band %d may be incorrect, and data access may be incomplete.",
|
|
nBand,
|
|
nGribDataXSize, nGribDataYSize,
|
|
nRasterXSize, nRasterYSize,
|
|
nBand );
|
|
}
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr GRIBRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
|
|
int nBlockYOff,
|
|
void * pImage )
|
|
|
|
{
|
|
CPLErr eErr = LoadData();
|
|
if (eErr != CE_None)
|
|
return eErr;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* The image as read is always upside down to our normal */
|
|
/* orientation so we need to effectively flip it at this */
|
|
/* point. We also need to deal with bands that are a different */
|
|
/* size than the dataset as a whole. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( nGribDataXSize == nRasterXSize
|
|
&& nGribDataYSize == nRasterYSize )
|
|
{
|
|
// Simple 1:1 case.
|
|
memcpy(pImage,
|
|
m_Grib_Data + nRasterXSize * (nRasterYSize - nBlockYOff - 1),
|
|
nRasterXSize * sizeof(double));
|
|
|
|
return CE_None;
|
|
}
|
|
else
|
|
{
|
|
memset( pImage, 0, sizeof(double)*nRasterXSize );
|
|
|
|
if( nBlockYOff >= nGribDataYSize ) // off image?
|
|
return CE_None;
|
|
|
|
int nCopyWords = MIN(nRasterXSize,nGribDataXSize);
|
|
|
|
memcpy( pImage,
|
|
m_Grib_Data + nGribDataXSize*(nGribDataYSize-nBlockYOff-1),
|
|
nCopyWords * sizeof(double) );
|
|
|
|
return CE_None;
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetNoDataValue() */
|
|
/************************************************************************/
|
|
|
|
double GRIBRasterBand::GetNoDataValue( int *pbSuccess )
|
|
{
|
|
CPLErr eErr = LoadData();
|
|
if (eErr != CE_None ||
|
|
m_Grib_MetaData == NULL ||
|
|
m_Grib_MetaData->gridAttrib.f_miss == 0)
|
|
{
|
|
if (pbSuccess)
|
|
*pbSuccess = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
if (m_Grib_MetaData->gridAttrib.f_miss == 2)
|
|
{
|
|
/* what TODO ? */
|
|
CPLDebug("GRIB", "Secondary missing value also set for band %d : %f",
|
|
nBand, m_Grib_MetaData->gridAttrib.missSec);
|
|
}
|
|
|
|
if (pbSuccess)
|
|
*pbSuccess = TRUE;
|
|
return m_Grib_MetaData->gridAttrib.missPri;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ReadGribData() */
|
|
/************************************************************************/
|
|
|
|
void GRIBRasterBand::ReadGribData( DataSource & fp, sInt4 start, int subgNum, double** data, grib_MetaData** metaData)
|
|
{
|
|
/* Initialisation, for calling the ReadGrib2Record function */
|
|
sInt4 f_endMsg = 1; /* 1 if we read the last grid in a GRIB message, or we haven't read any messages. */
|
|
// int subgNum = 0; /* The subgrid in the message that we are interested in. */
|
|
sChar f_unit = 2; /* None = 0, English = 1, Metric = 2 */
|
|
double majEarth = 0; /* -radEarth if < 6000 ignore, otherwise use this to
|
|
* override the radEarth in the GRIB1 or GRIB2
|
|
* message. Needed because NCEP uses 6371.2 but GRIB1 could only state 6367.47. */
|
|
double minEarth = 0; /* -minEarth if < 6000 ignore, otherwise use this to
|
|
* override the minEarth in the GRIB1 or GRIB2 message. */
|
|
sChar f_SimpleVer = 4; /* Which version of the simple NDFD Weather table to
|
|
* use. (1 is 6/2003) (2 is 1/2004) (3 is 2/2004)
|
|
* (4 is 11/2004) (default 4) */
|
|
LatLon lwlf; /* lower left corner (cookie slicing) -lwlf */
|
|
LatLon uprt; /* upper right corner (cookie slicing) -uprt */
|
|
IS_dataType is; /* Un-parsed meta data for this GRIB2 message. As well as some memory used by the unpacker. */
|
|
|
|
lwlf.lat = -100; // lat == -100 instructs the GRIB decoder that we don't want a subgrid
|
|
|
|
IS_Init (&is);
|
|
|
|
const char* pszGribNormalizeUnits = CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
|
|
if ( !CSLTestBoolean(pszGribNormalizeUnits) )
|
|
f_unit = 0; /* do not normalize units to metric */
|
|
|
|
/* Read GRIB message from file position "start". */
|
|
fp.DataSourceFseek(start, SEEK_SET);
|
|
uInt4 grib_DataLen = 0; /* Size of Grib_Data. */
|
|
*metaData = new grib_MetaData();
|
|
MetaInit (*metaData);
|
|
ReadGrib2Record (fp, f_unit, data, &grib_DataLen, *metaData, &is, subgNum,
|
|
majEarth, minEarth, f_SimpleVer, &f_endMsg, &lwlf, &uprt);
|
|
|
|
char * errMsg = errSprintf(NULL); // no intention to show errors, just swallow it and free the memory
|
|
if( errMsg != NULL )
|
|
CPLDebug( "GRIB", "%s", errMsg );
|
|
free(errMsg);
|
|
IS_Free(&is);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* UncacheData() */
|
|
/************************************************************************/
|
|
|
|
void GRIBRasterBand::UncacheData()
|
|
{
|
|
if (m_Grib_Data)
|
|
free (m_Grib_Data);
|
|
m_Grib_Data = NULL;
|
|
if (m_Grib_MetaData)
|
|
{
|
|
MetaFree( m_Grib_MetaData );
|
|
delete m_Grib_MetaData;
|
|
}
|
|
m_Grib_MetaData = NULL;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~GRIBRasterBand() */
|
|
/************************************************************************/
|
|
|
|
GRIBRasterBand::~GRIBRasterBand()
|
|
{
|
|
CPLFree(longFstLevel);
|
|
UncacheData();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* GRIBDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
GRIBDataset::GRIBDataset()
|
|
|
|
{
|
|
poTransform = NULL;
|
|
pszProjection = CPLStrdup("");
|
|
adfGeoTransform[0] = 0.0;
|
|
adfGeoTransform[1] = 1.0;
|
|
adfGeoTransform[2] = 0.0;
|
|
adfGeoTransform[3] = 0.0;
|
|
adfGeoTransform[4] = 0.0;
|
|
adfGeoTransform[5] = 1.0;
|
|
|
|
nCachedBytes = 0;
|
|
/* Switch caching strategy once 100 MB threshold is reached */
|
|
/* Why 100 MB ? --> why not ! */
|
|
nCachedBytesThreshold = ((GIntBig)atoi(CPLGetConfigOption("GRIB_CACHEMAX", "100"))) * 1024 * 1024;
|
|
bCacheOnlyOneBand = FALSE;
|
|
poLastUsedBand = NULL;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~GRIBDataset() */
|
|
/************************************************************************/
|
|
|
|
GRIBDataset::~GRIBDataset()
|
|
|
|
{
|
|
FlushCache();
|
|
if( fp != NULL )
|
|
VSIFCloseL( fp );
|
|
|
|
CPLFree( pszProjection );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr GRIBDataset::GetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetProjectionRef() */
|
|
/************************************************************************/
|
|
|
|
const char *GRIBDataset::GetProjectionRef()
|
|
|
|
{
|
|
return pszProjection;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int GRIBDataset::Identify( GDALOpenInfo * poOpenInfo )
|
|
{
|
|
if (poOpenInfo->nHeaderBytes < 8)
|
|
return FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Does a part of what ReadSECT0() but in a thread-safe way. */
|
|
/* -------------------------------------------------------------------- */
|
|
int i;
|
|
for(i=0;i<poOpenInfo->nHeaderBytes-3;i++)
|
|
{
|
|
if (EQUALN((const char*)poOpenInfo->pabyHeader + i, "GRIB", 4) ||
|
|
EQUALN((const char*)poOpenInfo->pabyHeader + i, "TDLP", 4))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *GRIBDataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if( !Identify(poOpenInfo) )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* A fast "probe" on the header that is partially read in memory. */
|
|
/* -------------------------------------------------------------------- */
|
|
char *buff = NULL;
|
|
uInt4 buffLen = 0;
|
|
sInt4 sect0[SECT0LEN_WORD];
|
|
uInt4 gribLen;
|
|
int version;
|
|
// grib is not thread safe, make sure not to cause problems
|
|
// for other thread safe formats
|
|
|
|
CPLMutexHolderD(&hGRIBMutex);
|
|
MemoryDataSource mds (poOpenInfo->pabyHeader, poOpenInfo->nHeaderBytes);
|
|
if (ReadSECT0 (mds, &buff, &buffLen, -1, sect0, &gribLen, &version) < 0) {
|
|
free (buff);
|
|
char * errMsg = errSprintf(NULL);
|
|
if( errMsg != NULL && strstr(errMsg,"Ran out of file") == NULL )
|
|
CPLDebug( "GRIB", "%s", errMsg );
|
|
free(errMsg);
|
|
return NULL;
|
|
}
|
|
free(buff);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Confirm the requested access is supported. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poOpenInfo->eAccess == GA_Update )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The GRIB driver does not support update access to existing"
|
|
" datasets.\n" );
|
|
return NULL;
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
GRIBDataset *poDS;
|
|
|
|
poDS = new GRIBDataset();
|
|
|
|
poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "r" );
|
|
|
|
/* Check the return values */
|
|
if (!poDS->fp) {
|
|
// we have no FP, so we don't have anywhere to read from
|
|
char * errMsg = errSprintf(NULL);
|
|
if( errMsg != NULL )
|
|
CPLDebug( "GRIB", "%s", errMsg );
|
|
free(errMsg);
|
|
|
|
CPLError( CE_Failure, CPLE_OpenFailed, "Error (%d) opening file %s", errno, poOpenInfo->pszFilename);
|
|
CPLReleaseMutex(hGRIBMutex); // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own hGRIBMutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hGRIBMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the header. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Make an inventory of the GRIB file. */
|
|
/* The inventory does not contain all the information needed for */
|
|
/* creating the RasterBands (especially the x and y size), therefore */
|
|
/* the first GRIB band is also read for some additional metadata. */
|
|
/* The band-data that is read is stored into the first RasterBand, */
|
|
/* simply so that the same portion of the file is not read twice. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
VSIFSeekL( poDS->fp, 0, SEEK_SET );
|
|
|
|
FileDataSource grib_fp (poDS->fp);
|
|
|
|
inventoryType *Inv = NULL; /* Contains an GRIB2 message inventory of the file */
|
|
uInt4 LenInv = 0; /* size of Inv (also # of GRIB2 messages) */
|
|
int msgNum =0; /* The messageNumber during the inventory. */
|
|
|
|
if (GRIB2Inventory (grib_fp, &Inv, &LenInv, 0, &msgNum) <= 0 )
|
|
{
|
|
char * errMsg = errSprintf(NULL);
|
|
if( errMsg != NULL )
|
|
CPLDebug( "GRIB", "%s", errMsg );
|
|
free(errMsg);
|
|
|
|
CPLError( CE_Failure, CPLE_OpenFailed,
|
|
"%s is a grib file, but no raster dataset was successfully identified.",
|
|
poOpenInfo->pszFilename );
|
|
CPLReleaseMutex(hGRIBMutex); // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own hGRIBMutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hGRIBMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
GRIBRasterBand *gribBand;
|
|
for (uInt4 i = 0; i < LenInv; ++i)
|
|
{
|
|
uInt4 bandNr = i+1;
|
|
if (bandNr == 1)
|
|
{
|
|
// important: set DataSet extents before creating first RasterBand in it
|
|
double * data = NULL;
|
|
grib_MetaData* metaData;
|
|
GRIBRasterBand::ReadGribData(grib_fp, 0, Inv[i].subgNum, &data, &metaData);
|
|
if (data == 0 || metaData->gds.Nx < 1 || metaData->gds.Ny < 1)
|
|
{
|
|
CPLError( CE_Failure, CPLE_OpenFailed,
|
|
"%s is a grib file, but no raster dataset was successfully identified.",
|
|
poOpenInfo->pszFilename );
|
|
CPLReleaseMutex(hGRIBMutex); // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own hGRIBMutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hGRIBMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
poDS->SetGribMetaData(metaData); // set the DataSet's x,y size, georeference and projection from the first GRIB band
|
|
gribBand = new GRIBRasterBand( poDS, bandNr, Inv+i);
|
|
|
|
if( Inv->GribVersion == 2 )
|
|
gribBand->FindPDSTemplate();
|
|
|
|
gribBand->m_Grib_Data = data;
|
|
gribBand->m_Grib_MetaData = metaData;
|
|
}
|
|
else
|
|
{
|
|
gribBand = new GRIBRasterBand( poDS, bandNr, Inv+i );
|
|
if( CSLTestBoolean( CPLGetConfigOption( "GRIB_PDS_ALL_BANDS", "ON" ) ) )
|
|
{
|
|
if( Inv->GribVersion == 2 )
|
|
gribBand->FindPDSTemplate();
|
|
}
|
|
}
|
|
poDS->SetBand( bandNr, gribBand);
|
|
GRIB2InventoryFree (Inv + i);
|
|
}
|
|
free (Inv);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
|
|
CPLReleaseMutex(hGRIBMutex); // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own hGRIBMutex
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for external overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename, poOpenInfo->GetSiblingFiles() );
|
|
CPLAcquireMutex(hGRIBMutex, 1000.0);
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetMetadata() */
|
|
/************************************************************************/
|
|
|
|
void GRIBDataset::SetGribMetaData(grib_MetaData* meta)
|
|
{
|
|
nRasterXSize = meta->gds.Nx;
|
|
nRasterYSize = meta->gds.Ny;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Image projection. */
|
|
/* -------------------------------------------------------------------- */
|
|
OGRSpatialReference oSRS;
|
|
|
|
switch(meta->gds.projType)
|
|
{
|
|
case GS3_LATLON:
|
|
case GS3_GAUSSIAN_LATLON:
|
|
// No projection, only latlon system (geographic)
|
|
break;
|
|
case GS3_MERCATOR:
|
|
oSRS.SetMercator(meta->gds.meshLat, meta->gds.orientLon,
|
|
1.0, 0.0, 0.0);
|
|
break;
|
|
case GS3_POLAR:
|
|
oSRS.SetPS(meta->gds.meshLat, meta->gds.orientLon,
|
|
meta->gds.scaleLat1,
|
|
0.0, 0.0);
|
|
break;
|
|
case GS3_LAMBERT:
|
|
oSRS.SetLCC(meta->gds.scaleLat1, meta->gds.scaleLat2,
|
|
meta->gds.meshLat, meta->gds.orientLon,
|
|
0.0, 0.0); // set projection
|
|
break;
|
|
|
|
|
|
case GS3_ORTHOGRAPHIC:
|
|
|
|
//oSRS.SetOrthographic(0.0, meta->gds.orientLon,
|
|
// meta->gds.lon2, meta->gds.lat2);
|
|
//oSRS.SetGEOS(meta->gds.orientLon, meta->gds.stretchFactor, meta->gds.lon2, meta->gds.lat2);
|
|
oSRS.SetGEOS( 0, 35785831, 0, 0 ); // hardcoded for now, I don't know yet how to parse the meta->gds section
|
|
break;
|
|
case GS3_EQUATOR_EQUIDIST:
|
|
break;
|
|
case GS3_AZIMUTH_RANGE:
|
|
break;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Earth model */
|
|
/* -------------------------------------------------------------------- */
|
|
double a = meta->gds.majEarth * 1000.0; // in meters
|
|
double b = meta->gds.minEarth * 1000.0;
|
|
if( a == 0 && b == 0 )
|
|
{
|
|
a = 6377563.396;
|
|
b = 6356256.910;
|
|
}
|
|
|
|
if (meta->gds.f_sphere)
|
|
{
|
|
oSRS.SetGeogCS( "Coordinate System imported from GRIB file",
|
|
NULL,
|
|
"Sphere",
|
|
a, 0.0 );
|
|
}
|
|
else
|
|
{
|
|
double fInv = a/(a-b);
|
|
oSRS.SetGeogCS( "Coordinate System imported from GRIB file",
|
|
NULL,
|
|
"Spheroid imported from GRIB file",
|
|
a, fInv );
|
|
}
|
|
|
|
OGRSpatialReference oLL; // construct the "geographic" part of oSRS
|
|
oLL.CopyGeogCSFrom( &oSRS );
|
|
|
|
double rMinX;
|
|
double rMaxY;
|
|
double rPixelSizeX;
|
|
double rPixelSizeY;
|
|
if (meta->gds.projType == GS3_ORTHOGRAPHIC)
|
|
{
|
|
//rMinX = -meta->gds.Dx * (meta->gds.Nx / 2); // This is what should work, but it doesn't .. Dx seems to have an inverse relation with pixel size
|
|
//rMaxY = meta->gds.Dy * (meta->gds.Ny / 2);
|
|
const double geosExtentInMeters = 11137496.552; // hardcoded for now, assumption: GEOS projection, full disc (like MSG)
|
|
rMinX = -(geosExtentInMeters / 2);
|
|
rMaxY = geosExtentInMeters / 2;
|
|
rPixelSizeX = geosExtentInMeters / meta->gds.Nx;
|
|
rPixelSizeY = geosExtentInMeters / meta->gds.Ny;
|
|
}
|
|
else if( oSRS.IsProjected() )
|
|
{
|
|
rMinX = meta->gds.lon1; // longitude in degrees, to be transformed to meters (or degrees in case of latlon)
|
|
rMaxY = meta->gds.lat1; // latitude in degrees, to be transformed to meters
|
|
OGRCoordinateTransformation *poTransformLLtoSRS = OGRCreateCoordinateTransformation( &(oLL), &(oSRS) );
|
|
if ((poTransformLLtoSRS != NULL) && poTransformLLtoSRS->Transform( 1, &rMinX, &rMaxY )) // transform it to meters
|
|
{
|
|
if (meta->gds.scan == GRIB2BIT_2) // Y is minY, GDAL wants maxY
|
|
rMaxY += (meta->gds.Ny - 1) * meta->gds.Dy; // -1 because we GDAL needs the coordinates of the centre of the pixel
|
|
rPixelSizeX = meta->gds.Dx;
|
|
rPixelSizeY = meta->gds.Dy;
|
|
}
|
|
else
|
|
{
|
|
rMinX = 0.0;
|
|
rMaxY = 0.0;
|
|
|
|
rPixelSizeX = 1.0;
|
|
rPixelSizeY = -1.0;
|
|
|
|
oSRS.Clear();
|
|
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Unable to perform coordinate transformations, so the correct\n"
|
|
"projected geotransform could not be deduced from the lat/long\n"
|
|
"control points. Defaulting to ungeoreferenced." );
|
|
}
|
|
delete poTransformLLtoSRS;
|
|
}
|
|
else
|
|
{
|
|
rMinX = meta->gds.lon1; // longitude in degrees, to be transformed to meters (or degrees in case of latlon)
|
|
rMaxY = meta->gds.lat1; // latitude in degrees, to be transformed to meters
|
|
|
|
double rMinY = meta->gds.lat2;
|
|
if (meta->gds.lat2 > rMaxY)
|
|
{
|
|
rMaxY = meta->gds.lat2;
|
|
rMinY = meta->gds.lat1;
|
|
}
|
|
|
|
if( meta->gds.Nx == 1 )
|
|
rPixelSizeX = meta->gds.Dx;
|
|
else if (meta->gds.lon1 > meta->gds.lon2)
|
|
rPixelSizeX = (360.0 - (meta->gds.lon1 - meta->gds.lon2)) / (meta->gds.Nx - 1);
|
|
else
|
|
rPixelSizeX = (meta->gds.lon2 - meta->gds.lon1) / (meta->gds.Nx - 1);
|
|
|
|
if( meta->gds.Ny == 1 )
|
|
rPixelSizeY = meta->gds.Dy;
|
|
else
|
|
rPixelSizeY = (rMaxY - rMinY) / (meta->gds.Ny - 1);
|
|
|
|
// Do some sanity checks for cases that can't be handled by the above
|
|
// pixel size corrections. GRIB1 has a minimum precision of 0.001
|
|
// for latitudes and longitudes, so we'll allow a bit higher than that.
|
|
if (rPixelSizeX < 0 || fabs(rPixelSizeX - meta->gds.Dx) > 0.002)
|
|
rPixelSizeX = meta->gds.Dx;
|
|
|
|
if (rPixelSizeY < 0 || fabs(rPixelSizeY - meta->gds.Dy) > 0.002)
|
|
rPixelSizeY = meta->gds.Dy;
|
|
}
|
|
|
|
// http://gdal.org/gdal_datamodel.html :
|
|
// we need the top left corner of the top left pixel.
|
|
// At the moment we have the center of the pixel.
|
|
rMinX-=rPixelSizeX/2;
|
|
rMaxY+=rPixelSizeY/2;
|
|
|
|
adfGeoTransform[0] = rMinX;
|
|
adfGeoTransform[3] = rMaxY;
|
|
adfGeoTransform[1] = rPixelSizeX;
|
|
adfGeoTransform[5] = -rPixelSizeY;
|
|
|
|
CPLFree( pszProjection );
|
|
pszProjection = NULL;
|
|
oSRS.exportToWkt( &(pszProjection) );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALDeregister_GRIB() */
|
|
/************************************************************************/
|
|
|
|
static void GDALDeregister_GRIB(GDALDriver* )
|
|
{
|
|
if( hGRIBMutex != NULL )
|
|
{
|
|
CPLDestroyMutex(hGRIBMutex);
|
|
hGRIBMutex = NULL;
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_GRIB() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_GRIB()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if( GDALGetDriverByName( "GRIB" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "GRIB" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"GRIdded Binary (.grb)" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_grib.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "grb" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
|
|
poDriver->pfnOpen = GRIBDataset::Open;
|
|
poDriver->pfnIdentify = GRIBDataset::Identify;
|
|
poDriver->pfnUnloadDriver = GDALDeregister_GRIB;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|