mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
7084 lines
274 KiB
C++
7084 lines
274 KiB
C++
/******************************************************************************
|
|
* $Id: netcdfdataset.cpp 29200 2015-05-15 18:04:02Z rouault $
|
|
*
|
|
* Project: netCDF read/write Driver
|
|
* Purpose: GDAL bindings over netCDF library.
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2004, Frank Warmerdam
|
|
* Copyright (c) 2007-2013, Even Rouault <even dot rouault at mines-paris dot org>
|
|
* Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
|
|
*
|
|
* 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 "netcdfdataset.h"
|
|
#include "cpl_error.h"
|
|
#include "cpl_multiproc.h"
|
|
|
|
CPL_CVSID("$Id: netcdfdataset.cpp 29200 2015-05-15 18:04:02Z rouault $");
|
|
|
|
#include <map> //for NCDFWriteProjAttribs()
|
|
|
|
/* Internal function declarations */
|
|
|
|
int NCDFIsGDALVersionGTE(const char* pszVersion, int nTarget);
|
|
|
|
void NCDFAddGDALHistory( int fpImage,
|
|
const char * pszFilename, const char *pszOldHist,
|
|
const char * pszFunctionName );
|
|
|
|
void NCDFAddHistory(int fpImage, const char *pszAddHist, const char *pszOldHist);
|
|
|
|
int NCDFIsCfProjection( const char* pszProjection );
|
|
|
|
void NCDFWriteProjAttribs(const OGR_SRSNode *poPROJCS,
|
|
const char* pszProjection,
|
|
const int fpImage, const int NCDFVarID);
|
|
|
|
CPLErr NCDFSafeStrcat(char** ppszDest, char* pszSrc, size_t* nDestSize);
|
|
CPLErr NCDFSafeStrcpy(char** ppszDest, char* pszSrc, size_t* nDestSize);
|
|
|
|
/* var / attribute helper functions */
|
|
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
|
|
double *pdfValue );
|
|
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
|
|
char **pszValue );
|
|
CPLErr NCDFPutAttr( int nCdfId, int nVarId,
|
|
const char *pszAttrName, const char *pszValue );
|
|
CPLErr NCDFGet1DVar( int nCdfId, int nVarId, char **pszValue );//replace this where used
|
|
CPLErr NCDFPut1DVar( int nCdfId, int nVarId, const char *pszValue );
|
|
|
|
double NCDFGetDefaultNoDataValue( int nVarType );
|
|
|
|
/* dimension check functions */
|
|
int NCDFIsVarLongitude(int nCdfId, int nVarId=-1, const char * nVarName=NULL );
|
|
int NCDFIsVarLatitude(int nCdfId, int nVarId=-1, const char * nVarName=NULL );
|
|
int NCDFIsVarProjectionX( int nCdfId, int nVarId=-1, const char * pszVarName=NULL );
|
|
int NCDFIsVarProjectionY( int nCdfId, int nVarId=-1, const char * pszVarName=NULL );
|
|
int NCDFIsVarVerticalCoord(int nCdfId, int nVarId=-1, const char * nVarName=NULL );
|
|
int NCDFIsVarTimeCoord(int nCdfId, int nVarId=-1, const char * nVarName=NULL );
|
|
|
|
char **NCDFTokenizeArray( const char *pszValue );//replace this where used
|
|
void CopyMetadata( void *poDS, int fpImage, int CDFVarID,
|
|
const char *pszMatchPrefix=NULL, int bIsBand=TRUE );
|
|
|
|
// uncomment this for more debug ouput
|
|
// #define NCDF_DEBUG 1
|
|
|
|
CPLMutex *hNCMutex = NULL;
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* netCDFRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class netCDFRasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class netCDFDataset;
|
|
|
|
nc_type nc_datatype;
|
|
int cdfid;
|
|
int nZId;
|
|
int nZDim;
|
|
int nLevel;
|
|
int nBandXPos;
|
|
int nBandYPos;
|
|
int *panBandZPos;
|
|
int *panBandZLev;
|
|
int bNoDataSet;
|
|
double dfNoDataValue;
|
|
double adfValidRange[2];
|
|
double dfScale;
|
|
double dfOffset;
|
|
int bSignedData;
|
|
int status;
|
|
int bCheckLongitude;
|
|
|
|
CPLErr CreateBandMetadata( int *paDimIds );
|
|
template <class T> void CheckData ( void * pImage,
|
|
int nTmpBlockXSize, int nTmpBlockYSize,
|
|
int bCheckIsNan=FALSE ) ;
|
|
|
|
protected:
|
|
|
|
CPLXMLNode *SerializeToXML( const char *pszVRTPath );
|
|
|
|
public:
|
|
|
|
netCDFRasterBand( netCDFDataset *poDS,
|
|
int nZId,
|
|
int nZDim,
|
|
int nLevel,
|
|
int *panBandZLen,
|
|
int *panBandPos,
|
|
int *paDimIds,
|
|
int nBand );
|
|
netCDFRasterBand( netCDFDataset *poDS,
|
|
GDALDataType eType,
|
|
int nBand,
|
|
int bSigned=TRUE,
|
|
char *pszBandName=NULL,
|
|
char *pszLongName=NULL,
|
|
int nZId=-1,
|
|
int nZDim=2,
|
|
int nLevel=0,
|
|
int *panBandZLev=NULL,
|
|
int *panBandZPos=NULL,
|
|
int *paDimIds=NULL );
|
|
~netCDFRasterBand( );
|
|
|
|
virtual double GetNoDataValue( int * );
|
|
virtual CPLErr SetNoDataValue( double );
|
|
virtual double GetOffset( int * );
|
|
virtual CPLErr SetOffset( double );
|
|
virtual double GetScale( int * );
|
|
virtual CPLErr SetScale( double );
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
virtual CPLErr IWriteBlock( int, int, void * );
|
|
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* netCDFRasterBand() */
|
|
/************************************************************************/
|
|
|
|
netCDFRasterBand::netCDFRasterBand( netCDFDataset *poNCDFDS,
|
|
int nZId,
|
|
int nZDim,
|
|
int nLevel,
|
|
int *panBandZLev,
|
|
int *panBandZPos,
|
|
int *paDimIds,
|
|
int nBand )
|
|
|
|
{
|
|
double dfNoData = 0.0;
|
|
int bGotNoData = FALSE;
|
|
nc_type vartype=NC_NAT;
|
|
nc_type atttype=NC_NAT;
|
|
size_t attlen;
|
|
char szNoValueName[NCDF_MAX_STR_LEN];
|
|
|
|
this->poDS = poNCDFDS;
|
|
this->panBandZPos = NULL;
|
|
this->panBandZLev = NULL;
|
|
this->nBand = nBand;
|
|
this->nZId = nZId;
|
|
this->nZDim = nZDim;
|
|
this->nLevel = nLevel;
|
|
this->nBandXPos = panBandZPos[0];
|
|
this->nBandYPos = panBandZPos[1];
|
|
this->bSignedData = TRUE; //default signed, except for Byte
|
|
this->cdfid = poNCDFDS->GetCDFID();
|
|
this->status = NC_NOERR;
|
|
this->bCheckLongitude = FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Take care of all other dimmensions */
|
|
/* ------------------------------------------------------------------ */
|
|
if( nZDim > 2 ) {
|
|
this->panBandZPos =
|
|
(int *) CPLCalloc( nZDim-1, sizeof( int ) );
|
|
this->panBandZLev =
|
|
(int *) CPLCalloc( nZDim-1, sizeof( int ) );
|
|
|
|
for ( int i=0; i < nZDim - 2; i++ ){
|
|
this->panBandZPos[i] = panBandZPos[i+2];
|
|
this->panBandZLev[i] = panBandZLev[i];
|
|
}
|
|
}
|
|
|
|
this->dfNoDataValue = 0.0;
|
|
this->bNoDataSet = FALSE;
|
|
|
|
nRasterXSize = poDS->GetRasterXSize( );
|
|
nRasterYSize = poDS->GetRasterYSize( );
|
|
nBlockXSize = poDS->GetRasterXSize( );
|
|
nBlockYSize = 1;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get the type of the "z" variable, our target raster array. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( nc_inq_var( cdfid, nZId, NULL, &nc_datatype, NULL, NULL,
|
|
NULL ) != NC_NOERR ){
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Error in nc_var_inq() on 'z'." );
|
|
return;
|
|
}
|
|
|
|
if( nc_datatype == NC_BYTE )
|
|
eDataType = GDT_Byte;
|
|
#ifdef NETCDF_HAS_NC4
|
|
/* NC_UBYTE (unsigned byte) is only available for NC4 */
|
|
else if( nc_datatype == NC_UBYTE )
|
|
eDataType = GDT_Byte;
|
|
#endif
|
|
else if( nc_datatype == NC_CHAR )
|
|
eDataType = GDT_Byte;
|
|
else if( nc_datatype == NC_SHORT )
|
|
eDataType = GDT_Int16;
|
|
else if( nc_datatype == NC_INT )
|
|
eDataType = GDT_Int32;
|
|
else if( nc_datatype == NC_FLOAT )
|
|
eDataType = GDT_Float32;
|
|
else if( nc_datatype == NC_DOUBLE )
|
|
eDataType = GDT_Float64;
|
|
else
|
|
{
|
|
if( nBand == 1 )
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Unsupported netCDF datatype (%d), treat as Float32.",
|
|
(int) nc_datatype );
|
|
eDataType = GDT_Float32;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Find and set No Data for this variable */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* find attribute name, either _FillValue or missing_value */
|
|
status = nc_inq_att( cdfid, nZId,
|
|
_FillValue, &atttype, &attlen);
|
|
if( status == NC_NOERR ) {
|
|
strcpy(szNoValueName, _FillValue );
|
|
}
|
|
else {
|
|
status = nc_inq_att( cdfid, nZId,
|
|
"missing_value", &atttype, &attlen );
|
|
if( status == NC_NOERR ) {
|
|
strcpy( szNoValueName, "missing_value" );
|
|
}
|
|
}
|
|
|
|
/* fetch missing value */
|
|
if( status == NC_NOERR ) {
|
|
if ( NCDFGetAttr( cdfid, nZId, szNoValueName,
|
|
&dfNoData ) == CE_None )
|
|
bGotNoData = TRUE;
|
|
}
|
|
|
|
/* if NoData was not found, use the default value */
|
|
if ( ! bGotNoData ) {
|
|
nc_inq_vartype( cdfid, nZId, &vartype );
|
|
dfNoData = NCDFGetDefaultNoDataValue( vartype );
|
|
bGotNoData = TRUE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"did not get nodata value for variable #%d, using default %f",
|
|
nZId, dfNoData );
|
|
}
|
|
|
|
/* set value */
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData );
|
|
#endif
|
|
SetNoDataValue( dfNoData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for valid_range or valid_min/valid_max */
|
|
/* -------------------------------------------------------------------- */
|
|
/* set valid_range to nodata, then check for actual values */
|
|
adfValidRange[0] = dfNoData;
|
|
adfValidRange[1] = dfNoData;
|
|
/* first look for valid_range */
|
|
int bGotValidRange = FALSE;
|
|
status = nc_inq_att( cdfid, nZId,
|
|
"valid_range", &atttype, &attlen);
|
|
if( (status == NC_NOERR) && (attlen == 2)) {
|
|
int vrange[2];
|
|
int vmin, vmax;
|
|
status = nc_get_att_int( cdfid, nZId,
|
|
"valid_range", vrange );
|
|
if( status == NC_NOERR ) {
|
|
bGotValidRange = TRUE;
|
|
adfValidRange[0] = vrange[0];
|
|
adfValidRange[1] = vrange[1];
|
|
}
|
|
/* if not found look for valid_min and valid_max */
|
|
else {
|
|
status = nc_get_att_int( cdfid, nZId,
|
|
"valid_min", &vmin );
|
|
if( status == NC_NOERR ) {
|
|
adfValidRange[0] = vmin;
|
|
status = nc_get_att_int( cdfid, nZId,
|
|
"valid_max", &vmax );
|
|
if( status == NC_NOERR ) {
|
|
adfValidRange[1] = vmax;
|
|
bGotValidRange = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Special For Byte Bands: check for signed/unsigned byte */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( nc_datatype == NC_BYTE ) {
|
|
|
|
/* netcdf uses signed byte by default, but GDAL uses unsigned by default */
|
|
/* This may cause unexpected results, but is needed for back-compat */
|
|
if ( poNCDFDS->bIsGdalFile )
|
|
this->bSignedData = FALSE;
|
|
else
|
|
this->bSignedData = TRUE;
|
|
|
|
/* For NC4 format NC_BYTE is signed, NC_UBYTE is unsigned */
|
|
if ( poNCDFDS->nFormat == NCDF_FORMAT_NC4 ) {
|
|
this->bSignedData = TRUE;
|
|
}
|
|
else {
|
|
/* if we got valid_range, test for signed/unsigned range */
|
|
/* http://www.unidata.ucar.edu/software/netcdf/docs/netcdf/Attribute-Conventions.html */
|
|
if ( bGotValidRange == TRUE ) {
|
|
/* If we got valid_range={0,255}, treat as unsigned */
|
|
if ( (adfValidRange[0] == 0) && (adfValidRange[1] == 255) ) {
|
|
bSignedData = FALSE;
|
|
/* reset valid_range */
|
|
adfValidRange[0] = dfNoData;
|
|
adfValidRange[1] = dfNoData;
|
|
}
|
|
/* If we got valid_range={-128,127}, treat as signed */
|
|
else if ( (adfValidRange[0] == -128) && (adfValidRange[1] == 127) ) {
|
|
bSignedData = TRUE;
|
|
/* reset valid_range */
|
|
adfValidRange[0] = dfNoData;
|
|
adfValidRange[1] = dfNoData;
|
|
}
|
|
}
|
|
/* else test for _Unsigned */
|
|
/* http://www.unidata.ucar.edu/software/netcdf/docs/BestPractices.html */
|
|
else {
|
|
char *pszTemp = NULL;
|
|
if ( NCDFGetAttr( cdfid, nZId, "_Unsigned", &pszTemp )
|
|
|
|
== CE_None ) {
|
|
if ( EQUAL(pszTemp,"true"))
|
|
bSignedData = FALSE;
|
|
else if ( EQUAL(pszTemp,"false"))
|
|
bSignedData = TRUE;
|
|
CPLFree( pszTemp );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bSignedData )
|
|
{
|
|
/* set PIXELTYPE=SIGNEDBYTE */
|
|
/* See http://trac.osgeo.org/gdal/wiki/rfc14_imagestructure */
|
|
SetMetadataItem( "PIXELTYPE", "SIGNEDBYTE", "IMAGE_STRUCTURE" );
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef NETCDF_HAS_NC4
|
|
if ( nc_datatype == NC_UBYTE )
|
|
this->bSignedData = FALSE;
|
|
#endif
|
|
|
|
CPLDebug( "GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
|
|
nc_datatype, eDataType, bSignedData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create Band Metadata */
|
|
/* -------------------------------------------------------------------- */
|
|
CreateBandMetadata( paDimIds );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Attempt to fetch the scale_factor and add_offset attributes for the */
|
|
/* variable and set them. If these values are not available, set */
|
|
/* offset to 0 and scale to 1 */
|
|
/* -------------------------------------------------------------------- */
|
|
double dfOff = 0.0;
|
|
double dfScale = 1.0;
|
|
|
|
if ( nc_inq_attid ( cdfid, nZId, CF_ADD_OFFSET, NULL) == NC_NOERR ) {
|
|
status = nc_get_att_double( cdfid, nZId, CF_ADD_OFFSET, &dfOff );
|
|
CPLDebug( "GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOff, status );
|
|
}
|
|
if ( nc_inq_attid ( cdfid, nZId,
|
|
CF_SCALE_FACTOR, NULL) == NC_NOERR ) {
|
|
status = nc_get_att_double( cdfid, nZId, CF_SCALE_FACTOR, &dfScale );
|
|
CPLDebug( "GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale, status );
|
|
}
|
|
SetOffset( dfOff );
|
|
SetScale( dfScale );
|
|
|
|
/* should we check for longitude values > 360 ? */
|
|
this->bCheckLongitude =
|
|
CSLTestBoolean(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES"))
|
|
&& NCDFIsVarLongitude( cdfid, nZId, NULL );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for variable chunking (netcdf-4 only) */
|
|
/* GDAL block size should be set to hdf5 chunk size */
|
|
/* -------------------------------------------------------------------- */
|
|
#ifdef NETCDF_HAS_NC4
|
|
int nTmpFormat = 0;
|
|
size_t chunksize[ MAX_NC_DIMS ];
|
|
status = nc_inq_format( cdfid, &nTmpFormat);
|
|
if( ( status == NC_NOERR ) && ( ( nTmpFormat == NCDF_FORMAT_NC4 ) ||
|
|
( nTmpFormat == NCDF_FORMAT_NC4C ) ) ) {
|
|
/* check for chunksize and set it as the blocksize (optimizes read) */
|
|
status = nc_inq_var_chunking( cdfid, nZId, &nTmpFormat, chunksize );
|
|
if( ( status == NC_NOERR ) && ( nTmpFormat == NC_CHUNKED ) ) {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"setting block size to chunk size : %ld x %ld\n",
|
|
chunksize[nZDim-1], chunksize[nZDim-2]);
|
|
nBlockXSize = (int) chunksize[nZDim-1];
|
|
nBlockYSize = (int) chunksize[nZDim-2];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Force block size to 1 scanline for bottom-up datasets if */
|
|
/* nBlockYSize != 1 */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poNCDFDS->bBottomUp && nBlockYSize != 1 ) {
|
|
nBlockXSize = nRasterXSize;
|
|
nBlockYSize = 1;
|
|
}
|
|
}
|
|
|
|
/* constructor in create mode */
|
|
/* if nZId and following variables are not passed, the band will have 2 dimensions */
|
|
/* TODO get metadata, missing val from band #1 if nZDim>2 */
|
|
netCDFRasterBand::netCDFRasterBand( netCDFDataset *poNCDFDS,
|
|
GDALDataType eType,
|
|
int nBand,
|
|
int bSigned,
|
|
char *pszBandName,
|
|
char *pszLongName,
|
|
int nZId,
|
|
int nZDim,
|
|
int nLevel,
|
|
int *panBandZLev,
|
|
int *panBandZPos,
|
|
int *paDimIds )
|
|
{
|
|
int status;
|
|
double dfNoData = 0.0;
|
|
char szTemp[NCDF_MAX_STR_LEN];
|
|
int bDefineVar = FALSE;
|
|
|
|
this->poDS = poNCDFDS;
|
|
this->nBand = nBand;
|
|
this->nZId = nZId;
|
|
this->nZDim = nZDim;
|
|
this->nLevel = nLevel;
|
|
this->panBandZPos = NULL;
|
|
this->panBandZLev = NULL;
|
|
this->nBandXPos = 1;
|
|
this->nBandYPos = 0;
|
|
this->bSignedData = bSigned;
|
|
|
|
this->status = NC_NOERR;
|
|
this->cdfid = poNCDFDS->GetCDFID();
|
|
this->bCheckLongitude = FALSE;
|
|
|
|
this->bNoDataSet = FALSE;
|
|
this->dfNoDataValue = 0.0;
|
|
|
|
nRasterXSize = poDS->GetRasterXSize( );
|
|
nRasterYSize = poDS->GetRasterYSize( );
|
|
nBlockXSize = poDS->GetRasterXSize( );
|
|
nBlockYSize = 1;
|
|
|
|
if ( poDS->GetAccess() != GA_Update ) {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"Dataset is not in update mode, wrong netCDFRasterBand constructor" );
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Take care of all other dimmensions */
|
|
/* ------------------------------------------------------------------ */
|
|
if ( nZDim > 2 && paDimIds != NULL ) {
|
|
|
|
this->nBandXPos = panBandZPos[0];
|
|
this->nBandYPos = panBandZPos[1];
|
|
|
|
this->panBandZPos =
|
|
(int *) CPLCalloc( nZDim-1, sizeof( int ) );
|
|
this->panBandZLev =
|
|
(int *) CPLCalloc( nZDim-1, sizeof( int ) );
|
|
|
|
for ( int i=0; i < nZDim - 2; i++ ){
|
|
this->panBandZPos[i] = panBandZPos[i+2];
|
|
this->panBandZLev[i] = panBandZLev[i];
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get the type of the "z" variable, our target raster array. */
|
|
/* -------------------------------------------------------------------- */
|
|
eDataType = eType;
|
|
|
|
switch ( eDataType )
|
|
{
|
|
case GDT_Byte:
|
|
nc_datatype = NC_BYTE;
|
|
#ifdef NETCDF_HAS_NC4
|
|
/* NC_UBYTE (unsigned byte) is only available for NC4 */
|
|
if ( ! bSignedData && (poNCDFDS->nFormat == NCDF_FORMAT_NC4) )
|
|
nc_datatype = NC_UBYTE;
|
|
#endif
|
|
break;
|
|
case GDT_Int16:
|
|
nc_datatype = NC_SHORT;
|
|
break;
|
|
case GDT_Int32:
|
|
nc_datatype = NC_INT;
|
|
break;
|
|
case GDT_Float32:
|
|
nc_datatype = NC_FLOAT;
|
|
break;
|
|
case GDT_Float64:
|
|
nc_datatype = NC_DOUBLE;
|
|
break;
|
|
default:
|
|
if( nBand == 1 )
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
|
|
(int) eDataType );
|
|
nc_datatype = NC_FLOAT;
|
|
break;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Define the variable if necessary (if nZId==-1) */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( nZId == -1 ) {
|
|
|
|
bDefineVar = TRUE;
|
|
|
|
/* make sure we are in define mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( TRUE );
|
|
|
|
if ( !pszBandName || EQUAL(pszBandName,"") )
|
|
sprintf( szTemp, "Band%d", nBand );
|
|
else
|
|
strcpy( szTemp, pszBandName );
|
|
|
|
if ( nZDim > 2 && paDimIds != NULL ) {
|
|
status = nc_def_var( cdfid, szTemp, nc_datatype,
|
|
nZDim, paDimIds, &nZId );
|
|
}
|
|
else {
|
|
int anBandDims[ 2 ];
|
|
anBandDims[0] = poNCDFDS->nYDimID;
|
|
anBandDims[1] = poNCDFDS->nXDimID;
|
|
status = nc_def_var( cdfid, szTemp, nc_datatype,
|
|
2, anBandDims, &nZId );
|
|
}
|
|
NCDF_ERR(status);
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d",
|
|
cdfid, szTemp, nc_datatype, nZId );
|
|
this->nZId = nZId;
|
|
|
|
if ( !pszLongName || EQUAL(pszLongName,"") )
|
|
sprintf( szTemp, "GDAL Band Number %d", nBand );
|
|
else
|
|
strcpy( szTemp, pszLongName );
|
|
status = nc_put_att_text( cdfid, nZId, CF_LNG_NAME,
|
|
strlen( szTemp ), szTemp );
|
|
NCDF_ERR(status);
|
|
|
|
poNCDFDS->DefVarDeflate(nZId, TRUE);
|
|
}
|
|
|
|
/* for Byte data add signed/unsigned info */
|
|
if ( eDataType == GDT_Byte ) {
|
|
|
|
if ( bDefineVar ) { //only add attributes if creating variable
|
|
CPLDebug( "GDAL_netCDF", "adding valid_range attributes for Byte Band" );
|
|
/* For unsigned NC_BYTE (except NC4 format) */
|
|
/* add valid_range and _Unsigned ( defined in CF-1 and NUG ) */
|
|
if ( (nc_datatype == NC_BYTE) && (poNCDFDS->nFormat != NCDF_FORMAT_NC4) ) {
|
|
short int adfValidRange[2];
|
|
if ( bSignedData ) {
|
|
adfValidRange[0] = -128;
|
|
adfValidRange[1] = 127;
|
|
status = nc_put_att_text( cdfid,nZId,
|
|
"_Unsigned", 5, "false" );
|
|
}
|
|
else {
|
|
adfValidRange[0] = 0;
|
|
adfValidRange[1] = 255;
|
|
status = nc_put_att_text( cdfid,nZId,
|
|
"_Unsigned", 4, "true" );
|
|
}
|
|
status=nc_put_att_short( cdfid,nZId, "valid_range",
|
|
NC_SHORT, 2, adfValidRange );
|
|
}
|
|
}
|
|
/* for unsigned byte set PIXELTYPE=SIGNEDBYTE */
|
|
/* See http://trac.osgeo.org/gdal/wiki/rfc14_imagestructure */
|
|
if ( bSignedData )
|
|
SetMetadataItem( "PIXELTYPE", "SIGNEDBYTE", "IMAGE_STRUCTURE" );
|
|
|
|
}
|
|
|
|
/* set default nodata */
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData );
|
|
#endif
|
|
dfNoData = NCDFGetDefaultNoDataValue( nc_datatype );
|
|
SetNoDataValue( dfNoData );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~netCDFRasterBand() */
|
|
/************************************************************************/
|
|
|
|
netCDFRasterBand::~netCDFRasterBand()
|
|
{
|
|
FlushCache();
|
|
if( panBandZPos )
|
|
CPLFree( panBandZPos );
|
|
if( panBandZLev )
|
|
CPLFree( panBandZLev );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetOffset() */
|
|
/************************************************************************/
|
|
double netCDFRasterBand::GetOffset( int *pbSuccess )
|
|
{
|
|
if( pbSuccess != NULL )
|
|
*pbSuccess = TRUE;
|
|
|
|
return dfOffset;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetOffset() */
|
|
/************************************************************************/
|
|
CPLErr netCDFRasterBand::SetOffset( double dfNewOffset )
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
dfOffset = dfNewOffset;
|
|
|
|
/* write value if in update mode */
|
|
if ( poDS->GetAccess() == GA_Update ) {
|
|
|
|
/* make sure we are in define mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( TRUE );
|
|
|
|
status = nc_put_att_double( cdfid, nZId, CF_ADD_OFFSET,
|
|
NC_DOUBLE, 1, &dfOffset );
|
|
|
|
NCDF_ERR(status);
|
|
if ( status == NC_NOERR )
|
|
return CE_None;
|
|
else
|
|
return CE_Failure;
|
|
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetScale() */
|
|
/************************************************************************/
|
|
double netCDFRasterBand::GetScale( int *pbSuccess )
|
|
{
|
|
if( pbSuccess != NULL )
|
|
*pbSuccess = TRUE;
|
|
return dfScale;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetScale() */
|
|
/************************************************************************/
|
|
CPLErr netCDFRasterBand::SetScale( double dfNewScale )
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
dfScale = dfNewScale;
|
|
|
|
/* write value if in update mode */
|
|
if ( poDS->GetAccess() == GA_Update ) {
|
|
|
|
/* make sure we are in define mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( TRUE );
|
|
|
|
status = nc_put_att_double( cdfid, nZId, CF_SCALE_FACTOR,
|
|
NC_DOUBLE, 1, &dfScale );
|
|
|
|
NCDF_ERR(status);
|
|
if ( status == NC_NOERR )
|
|
return CE_None;
|
|
else
|
|
return CE_Failure;
|
|
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetNoDataValue() */
|
|
/************************************************************************/
|
|
|
|
double netCDFRasterBand::GetNoDataValue( int * pbSuccess )
|
|
|
|
{
|
|
if( pbSuccess )
|
|
*pbSuccess = bNoDataSet;
|
|
|
|
if( bNoDataSet )
|
|
return dfNoDataValue;
|
|
else
|
|
return GDALPamRasterBand::GetNoDataValue( pbSuccess );
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* SetNoDataValue() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFRasterBand::SetNoDataValue( double dfNoData )
|
|
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
/* If already set to new value, don't do anything */
|
|
if ( bNoDataSet && CPLIsEqual( dfNoData, dfNoDataValue ) )
|
|
return CE_None;
|
|
|
|
/* write value if in update mode */
|
|
if ( poDS->GetAccess() == GA_Update ) {
|
|
|
|
/* netcdf-4 does not allow to set _FillValue after leaving define mode */
|
|
/* but it's ok if variable has not been written to, so only print debug */
|
|
/* see bug #4484 */
|
|
if ( bNoDataSet &&
|
|
( ((netCDFDataset *)poDS)->GetDefineMode() == FALSE ) ) {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"Setting NoDataValue to %.18g (previously set to %.18g) "
|
|
"but file is no longer in define mode (id #%d, band #%d)",
|
|
dfNoData, dfNoDataValue, cdfid, nBand );
|
|
}
|
|
#ifdef NCDF_DEBUG
|
|
else {
|
|
CPLDebug( "GDAL_netCDF", "Setting NoDataValue to %.18g (id #%d, band #%d)",
|
|
dfNoData, cdfid, nBand );
|
|
}
|
|
#endif
|
|
/* make sure we are in define mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( TRUE );
|
|
|
|
if ( eDataType == GDT_Byte) {
|
|
if ( bSignedData ) {
|
|
signed char cNoDataValue = (signed char) dfNoData;
|
|
status = nc_put_att_schar( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &cNoDataValue );
|
|
}
|
|
else {
|
|
unsigned char ucNoDataValue = (unsigned char) dfNoData;
|
|
status = nc_put_att_uchar( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &ucNoDataValue );
|
|
}
|
|
}
|
|
else if ( eDataType == GDT_Int16 ) {
|
|
short int nsNoDataValue = (short int) dfNoData;
|
|
status = nc_put_att_short( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &nsNoDataValue );
|
|
}
|
|
else if ( eDataType == GDT_Int32) {
|
|
int nNoDataValue = (int) dfNoData;
|
|
status = nc_put_att_int( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &nNoDataValue );
|
|
}
|
|
else if ( eDataType == GDT_Float32) {
|
|
float fNoDataValue = (float) dfNoData;
|
|
status = nc_put_att_float( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &fNoDataValue );
|
|
}
|
|
else
|
|
status = nc_put_att_double( cdfid, nZId, _FillValue,
|
|
nc_datatype, 1, &dfNoData );
|
|
|
|
NCDF_ERR(status);
|
|
|
|
/* update status if write worked */
|
|
if ( status == NC_NOERR ) {
|
|
dfNoDataValue = dfNoData;
|
|
bNoDataSet = TRUE;
|
|
return CE_None;
|
|
}
|
|
else
|
|
return CE_Failure;
|
|
|
|
}
|
|
|
|
dfNoDataValue = dfNoData;
|
|
bNoDataSet = TRUE;
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SerializeToXML() */
|
|
/************************************************************************/
|
|
|
|
CPLXMLNode *netCDFRasterBand::SerializeToXML( CPL_UNUSED const char *pszUnused )
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Overriden from GDALPamDataset to add only band histogram */
|
|
/* and statistics. See bug #4244. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( psPam == NULL )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Setup root node and attributes. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString oFmt;
|
|
|
|
CPLXMLNode *psTree;
|
|
|
|
psTree = CPLCreateXMLNode( NULL, CXT_Element, "PAMRasterBand" );
|
|
|
|
if( GetBand() > 0 )
|
|
CPLSetXMLValue( psTree, "#band", oFmt.Printf( "%d", GetBand() ) );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Histograms. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( psPam->psSavedHistograms != NULL )
|
|
CPLAddXMLChild( psTree, CPLCloneXMLTree( psPam->psSavedHistograms ) );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Metadata (statistics only). */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLXMLNode *psMD;
|
|
|
|
GDALMultiDomainMetadata oMDMDStats;
|
|
const char* papszMDStats[] = { "STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
|
|
"STATISTICS_MEAN", "STATISTICS_STDDEV",
|
|
NULL };
|
|
for ( int i=0; i<CSLCount((char**)papszMDStats); i++ ) {
|
|
if ( GetMetadataItem( papszMDStats[i] ) != NULL )
|
|
oMDMDStats.SetMetadataItem( papszMDStats[i],
|
|
GetMetadataItem(papszMDStats[i]) );
|
|
}
|
|
psMD = oMDMDStats.Serialize();
|
|
|
|
if( psMD != NULL )
|
|
{
|
|
if( psMD->psChild == NULL )
|
|
CPLDestroyXMLNode( psMD );
|
|
else
|
|
CPLAddXMLChild( psTree, psMD );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We don't want to return anything if we had no metadata to */
|
|
/* attach. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( psTree->psChild == NULL || psTree->psChild->psNext == NULL )
|
|
{
|
|
CPLDestroyXMLNode( psTree );
|
|
psTree = NULL;
|
|
}
|
|
|
|
return psTree;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CreateBandMetadata() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFRasterBand::CreateBandMetadata( int *paDimIds )
|
|
|
|
{
|
|
char szVarName[NC_MAX_NAME];
|
|
char szMetaName[NC_MAX_NAME];
|
|
char szMetaTemp[NCDF_MAX_STR_LEN];
|
|
char *pszMetaValue = NULL;
|
|
char szTemp[NC_MAX_NAME];
|
|
|
|
int nd;
|
|
int i,j;
|
|
int Sum = 1;
|
|
int Taken = 0;
|
|
int result = 0;
|
|
int status;
|
|
int nVarID = -1;
|
|
int nDims;
|
|
size_t start[1];
|
|
size_t count[1];
|
|
nc_type nVarType = NC_NAT;
|
|
int nAtt=0;
|
|
|
|
netCDFDataset *poDS = (netCDFDataset *) this->poDS;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute all dimensions from Band number and save in Metadata */
|
|
/* -------------------------------------------------------------------- */
|
|
nc_inq_varname( cdfid, nZId, szVarName );
|
|
nc_inq_varndims( cdfid, nZId, &nd );
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute multidimention band position */
|
|
/* */
|
|
/* BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)*/
|
|
/* if Data[2,3,4,x,y] */
|
|
/* */
|
|
/* BandPos0 = (nBand ) / (3*4) */
|
|
/* BandPos1 = (nBand - BandPos0*(3*4) ) / (4) */
|
|
/* BandPos2 = (nBand - BandPos0*(3*4) ) % (4) */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
sprintf( szMetaName,"NETCDF_VARNAME");
|
|
sprintf( szMetaTemp,"%s",szVarName);
|
|
SetMetadataItem( szMetaName, szMetaTemp );
|
|
if( nd == 3 ) {
|
|
Sum *= panBandZLev[0];
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Loop over non-spatial dimensions */
|
|
/* -------------------------------------------------------------------- */
|
|
for( i=0; i < nd-2 ; i++ ) {
|
|
|
|
if( i != nd - 2 -1 ) {
|
|
Sum = 1;
|
|
for( j=i+1; j < nd-2; j++ ) {
|
|
Sum *= panBandZLev[j];
|
|
}
|
|
result = (int) ( ( nLevel-Taken) / Sum );
|
|
}
|
|
else {
|
|
result = (int) ( ( nLevel-Taken) % Sum );
|
|
}
|
|
|
|
strcpy(szVarName,
|
|
poDS->papszDimName[paDimIds[panBandZPos[i]]] );
|
|
|
|
status=nc_inq_varid( cdfid, szVarName, &nVarID );
|
|
if( status != NC_NOERR ) {
|
|
/* Try to uppercase the first letter of the variable */
|
|
/* Note: why is this needed? leaving for safety */
|
|
szVarName[0]=(char) toupper(szVarName[0]);
|
|
status=nc_inq_varid( cdfid, szVarName, &nVarID );
|
|
}
|
|
|
|
status = nc_inq_vartype( cdfid, nVarID, &nVarType );
|
|
|
|
nDims = 0;
|
|
status = nc_inq_varndims( cdfid, nVarID, &nDims );
|
|
|
|
if( nDims == 1 ) {
|
|
count[0]=1;
|
|
start[0]=result;
|
|
switch( nVarType ) {
|
|
case NC_SHORT:
|
|
short sData;
|
|
status = nc_get_vara_short( cdfid, nVarID,
|
|
start,
|
|
count, &sData );
|
|
sprintf( szMetaTemp,"%d", sData );
|
|
break;
|
|
case NC_INT:
|
|
int nData;
|
|
status = nc_get_vara_int( cdfid, nVarID,
|
|
start,
|
|
count, &nData );
|
|
sprintf( szMetaTemp,"%d", nData );
|
|
break;
|
|
case NC_FLOAT:
|
|
float fData;
|
|
status = nc_get_vara_float( cdfid, nVarID,
|
|
start,
|
|
count, &fData );
|
|
CPLsprintf( szMetaTemp,"%.8g", fData );
|
|
break;
|
|
case NC_DOUBLE:
|
|
double dfData;
|
|
status = nc_get_vara_double( cdfid, nVarID,
|
|
start,
|
|
count, &dfData);
|
|
CPLsprintf( szMetaTemp,"%.16g", dfData );
|
|
break;
|
|
default:
|
|
CPLDebug( "GDAL_netCDF", "invalid dim %s, type=%d",
|
|
szMetaTemp, nVarType);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
sprintf( szMetaTemp,"%d", result+1);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Save dimension value */
|
|
/* -------------------------------------------------------------------- */
|
|
/* NOTE: removed #original_units as not part of CF-1 */
|
|
sprintf( szMetaName,"NETCDF_DIM_%s", szVarName );
|
|
SetMetadataItem( szMetaName, szMetaTemp );
|
|
|
|
Taken += result * Sum;
|
|
|
|
} // end loop non-spatial dimensions
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get all other metadata */
|
|
/* -------------------------------------------------------------------- */
|
|
nc_inq_varnatts( cdfid, nZId, &nAtt );
|
|
|
|
for( i=0; i < nAtt ; i++ ) {
|
|
|
|
status = nc_inq_attname( cdfid, nZId, i, szTemp);
|
|
// if(strcmp(szTemp,_FillValue) ==0) continue;
|
|
sprintf( szMetaName,"%s",szTemp);
|
|
|
|
if ( NCDFGetAttr( cdfid, nZId, szMetaName, &pszMetaValue)
|
|
== CE_None ) {
|
|
SetMetadataItem( szMetaName, pszMetaValue );
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF", "invalid Band metadata %s", szMetaName );
|
|
}
|
|
|
|
if ( pszMetaValue ) {
|
|
CPLFree( pszMetaValue );
|
|
pszMetaValue = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CheckData() */
|
|
/************************************************************************/
|
|
template <class T>
|
|
void netCDFRasterBand::CheckData ( void * pImage,
|
|
int nTmpBlockXSize, int nTmpBlockYSize,
|
|
int bCheckIsNan )
|
|
{
|
|
int i, j, k;
|
|
|
|
CPLAssert( pImage != NULL );
|
|
|
|
/* if this block is not a full block (in the x axis), we need to re-arrange the data
|
|
this is because partial blocks are not arranged the same way in netcdf and gdal */
|
|
if ( nTmpBlockXSize != nBlockXSize ) {
|
|
T* ptr = (T *) CPLCalloc( nTmpBlockXSize*nTmpBlockYSize, sizeof( T ) );
|
|
memcpy( ptr, pImage, nTmpBlockXSize*nTmpBlockYSize*sizeof( T ) );
|
|
for( j=0; j<nTmpBlockYSize; j++) {
|
|
k = j*nBlockXSize;
|
|
for( i=0; i<nTmpBlockXSize; i++,k++)
|
|
((T *) pImage)[k] = ptr[j*nTmpBlockXSize+i];
|
|
for( i=nTmpBlockXSize; i<nBlockXSize; i++,k++)
|
|
((T *) pImage)[k] = (T)dfNoDataValue;
|
|
}
|
|
CPLFree( ptr );
|
|
}
|
|
|
|
/* is valid data checking needed or requested? */
|
|
if ( (adfValidRange[0] != dfNoDataValue) ||
|
|
(adfValidRange[1] != dfNoDataValue) ||
|
|
bCheckIsNan ) {
|
|
for( j=0; j<nTmpBlockYSize; j++) {
|
|
// k moves along the gdal block, skipping the out-of-range pixels
|
|
k = j*nBlockXSize;
|
|
for( i=0; i<nTmpBlockXSize; i++,k++) {
|
|
/* check for nodata and nan */
|
|
if ( CPLIsEqual( (double) ((T *)pImage)[k], dfNoDataValue ) )
|
|
continue;
|
|
if( bCheckIsNan && CPLIsNan( (double) (( (T *) pImage))[k] ) ) {
|
|
( (T *)pImage )[k] = (T)dfNoDataValue;
|
|
continue;
|
|
}
|
|
/* check for valid_range */
|
|
if ( ( ( adfValidRange[0] != dfNoDataValue ) &&
|
|
( ((T *)pImage)[k] < (T)adfValidRange[0] ) )
|
|
||
|
|
( ( adfValidRange[1] != dfNoDataValue ) &&
|
|
( ((T *)pImage)[k] > (T)adfValidRange[1] ) ) ) {
|
|
( (T *)pImage )[k] = (T)dfNoDataValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if mininum longitude is > 180, subtract 360 from all
|
|
if not, disable checking for further calls (check just once)
|
|
only check first and last block elements since lon must be monotonic */
|
|
if ( bCheckLongitude &&
|
|
MIN( ((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize-1] ) > 180.0 ) {
|
|
for( j=0; j<nTmpBlockYSize; j++) {
|
|
k = j*nBlockXSize;
|
|
for( i=0; i<nTmpBlockXSize; i++,k++) {
|
|
if ( ! CPLIsEqual( (double) ((T *)pImage)[k], dfNoDataValue ) )
|
|
((T *)pImage )[k] -= 360.0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
bCheckLongitude = FALSE;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
|
|
{
|
|
size_t start[ MAX_NC_DIMS ];
|
|
size_t edge[ MAX_NC_DIMS ];
|
|
char pszName[ NCDF_MAX_STR_LEN ];
|
|
int i,j;
|
|
int Sum=-1;
|
|
int Taken=-1;
|
|
int nd=0;
|
|
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
*pszName='\0';
|
|
memset( start, 0, sizeof( start ) );
|
|
memset( edge, 0, sizeof( edge ) );
|
|
nc_inq_varndims ( cdfid, nZId, &nd );
|
|
|
|
#ifdef NCDF_DEBUG
|
|
if ( (nBlockYOff == 0) || (nBlockYOff == nRasterYSize-1) )
|
|
CPLDebug( "GDAL_netCDF", "netCDFRasterBand::IReadBlock( %d, %d, ... ) nBand=%d nd=%d",
|
|
nBlockXOff, nBlockYOff, nBand, nd );
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Locate X, Y and Z position in the array */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
start[nBandXPos] = nBlockXOff * nBlockXSize;
|
|
|
|
// check y order
|
|
if( ( ( netCDFDataset *) poDS )->bBottomUp ) {
|
|
#ifdef NCDF_DEBUG
|
|
if ( (nBlockYOff == 0) || (nBlockYOff == nRasterYSize-1) )
|
|
CPLDebug( "GDAL_netCDF",
|
|
"reading bottom-up dataset, nBlockYSize=%d nRasterYSize=%d",
|
|
nBlockYSize, nRasterYSize );
|
|
#endif
|
|
// check block size - return error if not 1
|
|
// reading upside-down rasters with nBlockYSize!=1 needs further development
|
|
// perhaps a simple solution is to invert geotransform and not use bottom-up
|
|
if ( nBlockYSize == 1 ) {
|
|
start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
|
|
}
|
|
else {
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"nBlockYSize = %d, only 1 supported when reading bottom-up dataset", nBlockYSize );
|
|
return CE_Failure;
|
|
}
|
|
} else {
|
|
start[nBandYPos] = nBlockYOff * nBlockYSize;
|
|
}
|
|
|
|
edge[nBandXPos] = nBlockXSize;
|
|
if ( ( start[nBandXPos] + edge[nBandXPos] ) > (size_t)nRasterXSize )
|
|
edge[nBandXPos] = nRasterXSize - start[nBandXPos];
|
|
edge[nBandYPos] = nBlockYSize;
|
|
if ( ( start[nBandYPos] + edge[nBandYPos] ) > (size_t)nRasterYSize )
|
|
edge[nBandYPos] = nRasterYSize - start[nBandYPos];
|
|
|
|
#ifdef NCDF_DEBUG
|
|
if ( (nBlockYOff == 0) || (nBlockYOff == nRasterYSize-1) )
|
|
CPLDebug( "GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
|
|
start[nBandXPos], start[nBandYPos], edge[nBandXPos], edge[nBandYPos],
|
|
( ( netCDFDataset *) poDS )->bBottomUp );
|
|
#endif
|
|
|
|
if( nd == 3 ) {
|
|
start[panBandZPos[0]] = nLevel; // z
|
|
edge [panBandZPos[0]] = 1;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute multidimention band position */
|
|
/* */
|
|
/* BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)*/
|
|
/* if Data[2,3,4,x,y] */
|
|
/* */
|
|
/* BandPos0 = (nBand ) / (3*4) */
|
|
/* BandPos1 = (nBand - (3*4) ) / (4) */
|
|
/* BandPos2 = (nBand - (3*4) ) % (4) */
|
|
/* -------------------------------------------------------------------- */
|
|
if (nd > 3)
|
|
{
|
|
Taken = 0;
|
|
for( i=0; i < nd-2 ; i++ )
|
|
{
|
|
if( i != nd - 2 -1 ) {
|
|
Sum = 1;
|
|
for( j=i+1; j < nd-2; j++ ) {
|
|
Sum *= panBandZLev[j];
|
|
}
|
|
start[panBandZPos[i]] = (int) ( ( nLevel-Taken) / Sum );
|
|
edge[panBandZPos[i]] = 1;
|
|
} else {
|
|
start[panBandZPos[i]] = (int) ( ( nLevel-Taken) % Sum );
|
|
edge[panBandZPos[i]] = 1;
|
|
}
|
|
Taken += start[panBandZPos[i]] * Sum;
|
|
}
|
|
}
|
|
|
|
/* make sure we are in data mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( FALSE );
|
|
|
|
/* read data according to type */
|
|
if( eDataType == GDT_Byte )
|
|
{
|
|
if (this->bSignedData)
|
|
{
|
|
status = nc_get_vara_schar( cdfid, nZId, start, edge,
|
|
(signed char *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<signed char>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
FALSE );
|
|
}
|
|
else {
|
|
status = nc_get_vara_uchar( cdfid, nZId, start, edge,
|
|
(unsigned char *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<unsigned char>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
FALSE );
|
|
}
|
|
}
|
|
|
|
else if( eDataType == GDT_Int16 )
|
|
{
|
|
status = nc_get_vara_short( cdfid, nZId, start, edge,
|
|
(short int *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<short int>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
FALSE );
|
|
}
|
|
else if( eDataType == GDT_Int32 )
|
|
{
|
|
if( sizeof(long) == 4 )
|
|
{
|
|
status = nc_get_vara_long( cdfid, nZId, start, edge,
|
|
(long int *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<long int>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
FALSE );
|
|
}
|
|
else
|
|
{
|
|
status = nc_get_vara_int( cdfid, nZId, start, edge,
|
|
(int *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<int>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
FALSE );
|
|
}
|
|
}
|
|
else if( eDataType == GDT_Float32 )
|
|
{
|
|
status = nc_get_vara_float( cdfid, nZId, start, edge,
|
|
(float *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<float>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
TRUE );
|
|
}
|
|
else if( eDataType == GDT_Float64 )
|
|
{
|
|
status = nc_get_vara_double( cdfid, nZId, start, edge,
|
|
(double *) pImage );
|
|
if ( status == NC_NOERR )
|
|
CheckData<double>( pImage, edge[nBandXPos], edge[nBandYPos],
|
|
TRUE );
|
|
}
|
|
else
|
|
status = NC_EBADTYPE;
|
|
|
|
if( status != NC_NOERR )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"netCDF scanline fetch failed: #%d (%s)",
|
|
status, nc_strerror( status ) );
|
|
return CE_Failure;
|
|
}
|
|
else
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IWriteBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFRasterBand::IWriteBlock( CPL_UNUSED int nBlockXOff,
|
|
int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
size_t start[ MAX_NC_DIMS];
|
|
size_t edge[ MAX_NC_DIMS ];
|
|
char pszName[ NCDF_MAX_STR_LEN ];
|
|
int i,j;
|
|
int Sum=-1;
|
|
int Taken=-1;
|
|
int nd;
|
|
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
#ifdef NCDF_DEBUG
|
|
if ( (nBlockYOff == 0) || (nBlockYOff == nRasterYSize-1) )
|
|
CPLDebug( "GDAL_netCDF", "netCDFRasterBand::IWriteBlock( %d, %d, ... ) nBand=%d",
|
|
nBlockXOff, nBlockYOff, nBand );
|
|
#endif
|
|
|
|
*pszName='\0';
|
|
memset( start, 0, sizeof( start ) );
|
|
memset( edge, 0, sizeof( edge ) );
|
|
nc_inq_varndims ( cdfid, nZId, &nd );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Locate X, Y and Z position in the array */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
start[nBandXPos] = 0; // x dim can move arround in array
|
|
// check y order
|
|
if( ( ( netCDFDataset *) poDS )->bBottomUp ) {
|
|
start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
|
|
} else {
|
|
start[nBandYPos] = nBlockYOff; // y
|
|
}
|
|
|
|
edge[nBandXPos] = nBlockXSize;
|
|
edge[nBandYPos] = 1;
|
|
|
|
if( nd == 3 ) {
|
|
start[panBandZPos[0]] = nLevel; // z
|
|
edge [panBandZPos[0]] = 1;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute multidimention band position */
|
|
/* */
|
|
/* BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)*/
|
|
/* if Data[2,3,4,x,y] */
|
|
/* */
|
|
/* BandPos0 = (nBand ) / (3*4) */
|
|
/* BandPos1 = (nBand - (3*4) ) / (4) */
|
|
/* BandPos2 = (nBand - (3*4) ) % (4) */
|
|
/* -------------------------------------------------------------------- */
|
|
if (nd > 3)
|
|
{
|
|
Taken = 0;
|
|
for( i=0; i < nd-2 ; i++ )
|
|
{
|
|
if( i != nd - 2 -1 ) {
|
|
Sum = 1;
|
|
for( j=i+1; j < nd-2; j++ ) {
|
|
Sum *= panBandZLev[j];
|
|
}
|
|
start[panBandZPos[i]] = (int) ( ( nLevel-Taken) / Sum );
|
|
edge[panBandZPos[i]] = 1;
|
|
} else {
|
|
start[panBandZPos[i]] = (int) ( ( nLevel-Taken) % Sum );
|
|
edge[panBandZPos[i]] = 1;
|
|
}
|
|
Taken += start[panBandZPos[i]] * Sum;
|
|
}
|
|
}
|
|
|
|
/* make sure we are in data mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( FALSE );
|
|
|
|
/* copy data according to type */
|
|
if( eDataType == GDT_Byte ) {
|
|
if ( this->bSignedData )
|
|
status = nc_put_vara_schar( cdfid, nZId, start, edge,
|
|
(signed char*) pImage);
|
|
else
|
|
status = nc_put_vara_uchar( cdfid, nZId, start, edge,
|
|
(unsigned char*) pImage);
|
|
}
|
|
else if( ( eDataType == GDT_UInt16 ) || ( eDataType == GDT_Int16 ) ) {
|
|
status = nc_put_vara_short( cdfid, nZId, start, edge,
|
|
(short int *) pImage);
|
|
}
|
|
else if( eDataType == GDT_Int32 ) {
|
|
status = nc_put_vara_int( cdfid, nZId, start, edge,
|
|
(int *) pImage);
|
|
}
|
|
else if( eDataType == GDT_Float32 ) {
|
|
status = nc_put_vara_float( cdfid, nZId, start, edge,
|
|
(float *) pImage);
|
|
}
|
|
else if( eDataType == GDT_Float64 ) {
|
|
status = nc_put_vara_double( cdfid, nZId, start, edge,
|
|
(double *) pImage);
|
|
}
|
|
else {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The NetCDF driver does not support GDAL data type %d",
|
|
eDataType );
|
|
status = NC_EBADTYPE;
|
|
}
|
|
NCDF_ERR(status);
|
|
|
|
if( status != NC_NOERR )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"netCDF scanline write failed: %s",
|
|
nc_strerror( status ) );
|
|
return CE_Failure;
|
|
}
|
|
else
|
|
return CE_None;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* netCDFDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
/************************************************************************/
|
|
/* netCDFDataset() */
|
|
/************************************************************************/
|
|
|
|
netCDFDataset::netCDFDataset()
|
|
|
|
{
|
|
/* basic dataset vars */
|
|
cdfid = -1;
|
|
papszSubDatasets = NULL;
|
|
papszMetadata = NULL;
|
|
bBottomUp = TRUE;
|
|
nFormat = NCDF_FORMAT_NONE;
|
|
bIsGdalFile = FALSE;
|
|
bIsGdalCfFile = FALSE;
|
|
|
|
/* projection/GT */
|
|
adfGeoTransform[0] = 0.0;
|
|
adfGeoTransform[1] = 1.0;
|
|
adfGeoTransform[2] = 0.0;
|
|
adfGeoTransform[3] = 0.0;
|
|
adfGeoTransform[4] = 0.0;
|
|
adfGeoTransform[5] = 1.0;
|
|
pszProjection = NULL;
|
|
nXDimID = -1;
|
|
nYDimID = -1;
|
|
bIsProjected = FALSE;
|
|
bIsGeographic = FALSE; /* can be not projected, and also not geographic */
|
|
pszCFProjection = NULL;
|
|
pszCFCoordinates = NULL;
|
|
|
|
/* state vars */
|
|
status = NC_NOERR;
|
|
bDefineMode = TRUE;
|
|
bSetProjection = FALSE;
|
|
bSetGeoTransform = FALSE;
|
|
bAddedProjectionVars = FALSE;
|
|
bAddedGridMappingRef = FALSE;
|
|
|
|
/* create vars */
|
|
papszCreationOptions = NULL;
|
|
nCompress = NCDF_COMPRESS_NONE;
|
|
nZLevel = NCDF_DEFLATE_LEVEL;
|
|
nCreateMode = NC_CLOBBER;
|
|
bSignedData = TRUE;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* ~netCDFDataset() */
|
|
/************************************************************************/
|
|
|
|
netCDFDataset::~netCDFDataset()
|
|
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s",
|
|
cdfid, osFilename.c_str() );
|
|
#endif
|
|
/* make sure projection is written if GeoTransform OR Projection are missing */
|
|
if( (GetAccess() == GA_Update) && (! bAddedProjectionVars) ) {
|
|
if ( bSetProjection && ! bSetGeoTransform )
|
|
AddProjectionVars();
|
|
else if ( bSetGeoTransform && ! bSetProjection )
|
|
AddProjectionVars();
|
|
// CPLError( CE_Warning, CPLE_AppDefined,
|
|
// "netCDFDataset::~netCDFDataset() Projection was not defined, projection will be missing" );
|
|
}
|
|
|
|
FlushCache();
|
|
|
|
/* make sure projection variable is written to band variable */
|
|
if( (GetAccess() == GA_Update) && ! bAddedGridMappingRef )
|
|
AddGridMappingRef();
|
|
|
|
CSLDestroy( papszMetadata );
|
|
CSLDestroy( papszSubDatasets );
|
|
CSLDestroy( papszCreationOptions );
|
|
|
|
if( pszProjection )
|
|
CPLFree( pszProjection );
|
|
if( pszCFProjection )
|
|
CPLFree( pszCFProjection );
|
|
if( pszCFCoordinates )
|
|
CPLFree( pszCFCoordinates );
|
|
|
|
if( cdfid > 0 ) {
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "calling nc_close( %d )", cdfid );
|
|
#endif
|
|
status = nc_close( cdfid );
|
|
NCDF_ERR(status);
|
|
}
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetDefineMode() */
|
|
/************************************************************************/
|
|
int netCDFDataset::SetDefineMode( int bNewDefineMode )
|
|
{
|
|
/* do nothing if already in new define mode
|
|
or if dataset is in read-only mode */
|
|
if ( ( bDefineMode == bNewDefineMode ) ||
|
|
( GetAccess() == GA_ReadOnly ) )
|
|
return CE_None;
|
|
|
|
CPLDebug( "GDAL_netCDF", "SetDefineMode(%d) old=%d",
|
|
bNewDefineMode, bDefineMode );
|
|
|
|
bDefineMode = bNewDefineMode;
|
|
|
|
if ( bDefineMode == TRUE )
|
|
status = nc_redef( cdfid );
|
|
else
|
|
status = nc_enddef( cdfid );
|
|
|
|
NCDF_ERR(status);
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetMetadataDomainList() */
|
|
/************************************************************************/
|
|
|
|
char **netCDFDataset::GetMetadataDomainList()
|
|
{
|
|
return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(),
|
|
TRUE,
|
|
"SUBDATASETS", NULL);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetMetadata() */
|
|
/************************************************************************/
|
|
char **netCDFDataset::GetMetadata( const char *pszDomain )
|
|
{
|
|
if( pszDomain != NULL && EQUALN( pszDomain, "SUBDATASETS", 11 ) )
|
|
return papszSubDatasets;
|
|
else
|
|
return GDALDataset::GetMetadata( pszDomain );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetProjectionRef() */
|
|
/************************************************************************/
|
|
|
|
const char * netCDFDataset::GetProjectionRef()
|
|
{
|
|
if( bSetProjection )
|
|
return pszProjection;
|
|
else
|
|
return GDALPamDataset::GetProjectionRef();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SerializeToXML() */
|
|
/************************************************************************/
|
|
|
|
CPLXMLNode *netCDFDataset::SerializeToXML( const char *pszUnused )
|
|
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Overriden from GDALPamDataset to add only band histogram */
|
|
/* and statistics. See bug #4244. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
CPLString oFmt;
|
|
|
|
if( psPam == NULL )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Setup root node and attributes. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLXMLNode *psDSTree;
|
|
|
|
psDSTree = CPLCreateXMLNode( NULL, CXT_Element, "PAMDataset" );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Process bands. */
|
|
/* -------------------------------------------------------------------- */
|
|
int iBand;
|
|
|
|
for( iBand = 0; iBand < GetRasterCount(); iBand++ )
|
|
{
|
|
CPLXMLNode *psBandTree;
|
|
|
|
netCDFRasterBand *poBand = (netCDFRasterBand *)
|
|
GetRasterBand(iBand+1);
|
|
|
|
if( poBand == NULL || !(poBand->GetMOFlags() & GMO_PAM_CLASS) )
|
|
continue;
|
|
|
|
psBandTree = poBand->SerializeToXML( pszUnused );
|
|
|
|
if( psBandTree != NULL )
|
|
CPLAddXMLChild( psDSTree, psBandTree );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We don't want to return anything if we had no metadata to */
|
|
/* attach. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( psDSTree->psChild == NULL )
|
|
{
|
|
CPLDestroyXMLNode( psDSTree );
|
|
psDSTree = NULL;
|
|
}
|
|
|
|
return psDSTree;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FetchCopyParm() */
|
|
/************************************************************************/
|
|
|
|
double netCDFDataset::FetchCopyParm( const char *pszGridMappingValue,
|
|
const char *pszParm, double dfDefault )
|
|
|
|
{
|
|
char szTemp[ MAX_NC_NAME ];
|
|
const char *pszValue;
|
|
|
|
strcpy(szTemp,pszGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, pszParm );
|
|
pszValue = CSLFetchNameValue(papszMetadata, szTemp);
|
|
|
|
if( pszValue )
|
|
{
|
|
return CPLAtofM(pszValue);
|
|
}
|
|
else
|
|
return dfDefault;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FetchStandardParallels() */
|
|
/************************************************************************/
|
|
|
|
char** netCDFDataset::FetchStandardParallels( const char *pszGridMappingValue )
|
|
{
|
|
char szTemp[ MAX_NC_NAME ];
|
|
const char *pszValue;
|
|
char **papszValues = NULL;
|
|
//cf-1.0 tags
|
|
strcpy( szTemp,pszGridMappingValue );
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, CF_PP_STD_PARALLEL );
|
|
pszValue = CSLFetchNameValue( papszMetadata, szTemp );
|
|
if( pszValue != NULL ) {
|
|
papszValues = NCDFTokenizeArray( pszValue );
|
|
}
|
|
//try gdal tags
|
|
else
|
|
{
|
|
strcpy( szTemp, pszGridMappingValue );
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, CF_PP_STD_PARALLEL_1 );
|
|
|
|
pszValue = CSLFetchNameValue( papszMetadata, szTemp );
|
|
|
|
if ( pszValue != NULL )
|
|
papszValues = CSLAddString( papszValues, pszValue );
|
|
|
|
strcpy( szTemp,pszGridMappingValue );
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, CF_PP_STD_PARALLEL_2 );
|
|
|
|
pszValue = CSLFetchNameValue( papszMetadata, szTemp );
|
|
|
|
if( pszValue != NULL )
|
|
papszValues = CSLAddString( papszValues, pszValue );
|
|
}
|
|
|
|
return papszValues;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetProjectionFromVar() */
|
|
/************************************************************************/
|
|
void netCDFDataset::SetProjectionFromVar( int nVarId )
|
|
{
|
|
size_t start[2], edge[2];
|
|
unsigned int i=0;
|
|
const char *pszValue = NULL;
|
|
int nVarProjectionID = -1;
|
|
char szVarName[ MAX_NC_NAME ];
|
|
char szTemp[ MAX_NC_NAME ];
|
|
char szGridMappingName[ MAX_NC_NAME ];
|
|
char szGridMappingValue[ MAX_NC_NAME ];
|
|
|
|
double dfStdP1=0.0;
|
|
double dfStdP2=0.0;
|
|
double dfCenterLat=0.0;
|
|
double dfCenterLon=0.0;
|
|
double dfScale=1.0;
|
|
double dfFalseEasting=0.0;
|
|
double dfFalseNorthing=0.0;
|
|
double dfCentralMeridian=0.0;
|
|
double dfEarthRadius=0.0;
|
|
double dfInverseFlattening=0.0;
|
|
double dfLonPrimeMeridian=0.0;
|
|
const char *pszPMName=NULL;
|
|
double dfSemiMajorAxis=0.0;
|
|
double dfSemiMinorAxis=0.0;
|
|
|
|
int bGotGeogCS = FALSE;
|
|
int bGotCfSRS = FALSE;
|
|
int bGotGdalSRS = FALSE;
|
|
int bGotCfGT = FALSE;
|
|
int bGotGdalGT = FALSE;
|
|
int bLookForWellKnownGCS = FALSE; //this could be a Config Option
|
|
|
|
/* These values from CF metadata */
|
|
OGRSpatialReference oSRS;
|
|
int nVarDimXID = -1;
|
|
int nVarDimYID = -1;
|
|
double *pdfXCoord = NULL;
|
|
double *pdfYCoord = NULL;
|
|
char szDimNameX[ MAX_NC_NAME ];
|
|
char szDimNameY[ MAX_NC_NAME ];
|
|
int nSpacingBegin=0;
|
|
int nSpacingMiddle=0;
|
|
int nSpacingLast=0;
|
|
int bLatSpacingOK=FALSE;
|
|
int bLonSpacingOK=FALSE;
|
|
size_t xdim = nRasterXSize;
|
|
size_t ydim = nRasterYSize;
|
|
|
|
const char *pszUnits = NULL;
|
|
|
|
/* These values from GDAL metadata */
|
|
const char *pszWKT = NULL;
|
|
const char *pszGeoTransform = NULL;
|
|
char **papszGeoTransform = NULL;
|
|
|
|
netCDFDataset * poDS = this; /* perhaps this should be removed for clarity */
|
|
|
|
/* temp variables to use in SetGeoTransform() and SetProjection() */
|
|
double adfTempGeoTransform[6];
|
|
char *pszTempProjection;
|
|
|
|
CPLDebug( "GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d )\n", nVarId );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get x/y range information. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
adfTempGeoTransform[0] = 0.0;
|
|
adfTempGeoTransform[1] = 1.0;
|
|
adfTempGeoTransform[2] = 0.0;
|
|
adfTempGeoTransform[3] = 0.0;
|
|
adfTempGeoTransform[4] = 0.0;
|
|
adfTempGeoTransform[5] = 1.0;
|
|
pszTempProjection = NULL;
|
|
|
|
if ( xdim == 1 || ydim == 1 ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"1-pixel width/height files not supported, xdim: %ld ydim: %ld",
|
|
(long)xdim, (long)ydim );
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for grid_mapping metadata */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
strcpy( szGridMappingValue, "" );
|
|
strcpy( szGridMappingName, "" );
|
|
|
|
nc_inq_varname( cdfid, nVarId, szVarName );
|
|
strcpy(szTemp,szVarName);
|
|
strcat(szTemp,"#");
|
|
strcat(szTemp,CF_GRD_MAPPING);
|
|
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
if( pszValue ) {
|
|
strcpy(szGridMappingName,szTemp);
|
|
strcpy(szGridMappingValue,pszValue);
|
|
}
|
|
|
|
if( !EQUAL( szGridMappingValue, "" ) ) {
|
|
|
|
/* Read grid_mapping metadata */
|
|
nc_inq_varid( cdfid, szGridMappingValue, &nVarProjectionID );
|
|
poDS->ReadAttributes( cdfid, nVarProjectionID );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for GDAL spatial_ref and GeoTransform within grid_mapping */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLDebug( "GDAL_netCDF", "got grid_mapping %s", szGridMappingValue );
|
|
strcpy( szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, NCDF_SPATIAL_REF);
|
|
|
|
pszWKT = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
|
|
if( pszWKT != NULL ) {
|
|
strcpy( szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, NCDF_GEOTRANSFORM);
|
|
pszGeoTransform = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get information about the file. */
|
|
/* -------------------------------------------------------------------- */
|
|
/* Was this file created by the GDAL netcdf driver? */
|
|
/* Was this file created by the newer (CF-conformant) driver? */
|
|
/* -------------------------------------------------------------------- */
|
|
/* 1) If GDAL netcdf metadata is set, and version >= 1.9, */
|
|
/* it was created with the new driver */
|
|
/* 2) Else, if spatial_ref and GeoTransform are present in the */
|
|
/* grid_mapping variable, it was created by the old driver */
|
|
/* -------------------------------------------------------------------- */
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, "NC_GLOBAL#GDAL");
|
|
|
|
if( pszValue && NCDFIsGDALVersionGTE(pszValue, 1900)) {
|
|
bIsGdalFile = TRUE;
|
|
bIsGdalCfFile = TRUE;
|
|
}
|
|
else if( pszWKT != NULL && pszGeoTransform != NULL ) {
|
|
bIsGdalFile = TRUE;
|
|
bIsGdalCfFile = FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set default bottom-up default value */
|
|
/* Y axis dimension and absence of GT can modify this value */
|
|
/* Override with Config option GDAL_NETCDF_BOTTOMUP */
|
|
/* -------------------------------------------------------------------- */
|
|
/* new driver is bottom-up by default */
|
|
if ( bIsGdalFile && ! bIsGdalCfFile )
|
|
poDS->bBottomUp = FALSE;
|
|
else
|
|
poDS->bBottomUp = TRUE;
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"bIsGdalFile=%d bIsGdalCfFile=%d bBottomUp=%d",
|
|
bIsGdalFile, bIsGdalCfFile, bBottomUp );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for dimension: lon */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
memset( szDimNameX, '\0', sizeof( char ) * MAX_NC_NAME );
|
|
memset( szDimNameY, '\0', sizeof( char ) * MAX_NC_NAME );
|
|
|
|
for( i = 0; (i < strlen( poDS->papszDimName[ poDS->nXDimID ] ) &&
|
|
i < 3 ); i++ ) {
|
|
szDimNameX[i]=(char)tolower( ( poDS->papszDimName[poDS->nXDimID] )[i] );
|
|
}
|
|
szDimNameX[3] = '\0';
|
|
for( i = 0; (i < strlen( poDS->papszDimName[ poDS->nYDimID ] ) &&
|
|
i < 3 ); i++ ) {
|
|
szDimNameY[i]=(char)tolower( ( poDS->papszDimName[poDS->nYDimID] )[i] );
|
|
}
|
|
szDimNameY[3] = '\0';
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read grid_mapping information and set projections */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( !( EQUAL(szGridMappingName,"" ) ) ) {
|
|
|
|
strcpy( szTemp, szGridMappingValue );
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, CF_GRD_MAPPING_NAME );
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
|
|
if( pszValue != NULL ) {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for datum/spheroid information */
|
|
/* -------------------------------------------------------------------- */
|
|
dfEarthRadius =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_EARTH_RADIUS,
|
|
-1.0 );
|
|
|
|
dfLonPrimeMeridian =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LONG_PRIME_MERIDIAN,
|
|
0.0 );
|
|
// should try to find PM name from its value if not Greenwich
|
|
if ( ! CPLIsEqual(dfLonPrimeMeridian,0.0) )
|
|
pszPMName = "unknown";
|
|
|
|
dfInverseFlattening =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_INVERSE_FLATTENING,
|
|
-1.0 );
|
|
|
|
dfSemiMajorAxis =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SEMI_MAJOR_AXIS,
|
|
-1.0 );
|
|
|
|
dfSemiMinorAxis =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SEMI_MINOR_AXIS,
|
|
-1.0 );
|
|
|
|
//see if semi-major exists if radius doesn't
|
|
if( dfEarthRadius < 0.0 )
|
|
dfEarthRadius = dfSemiMajorAxis;
|
|
|
|
//if still no radius, check old tag
|
|
if( dfEarthRadius < 0.0 )
|
|
dfEarthRadius = poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_EARTH_RADIUS_OLD,
|
|
-1.0 );
|
|
|
|
//has radius value
|
|
if( dfEarthRadius > 0.0 ) {
|
|
//check for inv_flat tag
|
|
if( dfInverseFlattening < 0.0 ) {
|
|
//no inv_flat tag, check for semi_minor
|
|
if( dfSemiMinorAxis < 0.0 ) {
|
|
//no way to get inv_flat, use sphere
|
|
oSRS.SetGeogCS( "unknown",
|
|
NULL,
|
|
"Sphere",
|
|
dfEarthRadius, 0.0,
|
|
pszPMName, dfLonPrimeMeridian );
|
|
bGotGeogCS = TRUE;
|
|
}
|
|
else {
|
|
if( dfSemiMajorAxis < 0.0 )
|
|
dfSemiMajorAxis = dfEarthRadius;
|
|
//set inv_flat using semi_minor/major
|
|
dfInverseFlattening = OSRCalcInvFlattening(dfSemiMajorAxis, dfSemiMinorAxis);
|
|
|
|
oSRS.SetGeogCS( "unknown",
|
|
NULL,
|
|
"Spheroid",
|
|
dfEarthRadius, dfInverseFlattening,
|
|
pszPMName, dfLonPrimeMeridian );
|
|
bGotGeogCS = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
oSRS.SetGeogCS( "unknown",
|
|
NULL,
|
|
"Spheroid",
|
|
dfEarthRadius, dfInverseFlattening,
|
|
pszPMName, dfLonPrimeMeridian );
|
|
bGotGeogCS = TRUE;
|
|
}
|
|
|
|
if ( bGotGeogCS )
|
|
CPLDebug( "GDAL_netCDF", "got spheroid from CF: (%f , %f)", dfEarthRadius, dfInverseFlattening );
|
|
|
|
}
|
|
//no radius, set as wgs84 as default?
|
|
else {
|
|
// This would be too indiscrimant. But we should set
|
|
// it if we know the data is geographic.
|
|
//oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Transverse Mercator */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( EQUAL( pszValue, CF_PT_TM ) ) {
|
|
|
|
dfScale =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SCALE_FACTOR_MERIDIAN, 1.0 );
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LONG_CENTRAL_MERIDIAN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetTM( dfCenterLat,
|
|
dfCenterLon,
|
|
dfScale,
|
|
dfFalseEasting,
|
|
dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Albers Equal Area */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( EQUAL( pszValue, CF_PT_AEA ) ) {
|
|
|
|
char **papszStdParallels = NULL;
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LONG_CENTRAL_MERIDIAN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
papszStdParallels =
|
|
FetchStandardParallels( szGridMappingValue );
|
|
|
|
if( papszStdParallels != NULL ) {
|
|
|
|
if ( CSLCount( papszStdParallels ) == 1 ) {
|
|
/* TODO CF-1 standard says it allows AEA to be encoded with only 1 standard parallel */
|
|
/* how should this actually map to a 2StdP OGC WKT version? */
|
|
CPLError( CE_Warning, CPLE_NotSupported,
|
|
"NetCDF driver import of AEA-1SP is not tested, using identical std. parallels\n" );
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
dfStdP2 = dfStdP1;
|
|
|
|
}
|
|
|
|
else if( CSLCount( papszStdParallels ) == 2 ) {
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
dfStdP2 = CPLAtofM( papszStdParallels[1] );
|
|
}
|
|
}
|
|
//old default
|
|
else {
|
|
dfStdP1 =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_STD_PARALLEL_1, 0.0 );
|
|
|
|
dfStdP2 =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_STD_PARALLEL_2, 0.0 );
|
|
}
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetACEA( dfStdP1, dfStdP2, dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
CSLDestroy( papszStdParallels );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Cylindrical Equal Area */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
else if( EQUAL( pszValue, CF_PT_CEA ) || EQUAL( pszValue, CF_PT_LCEA ) ) {
|
|
|
|
char **papszStdParallels = NULL;
|
|
|
|
papszStdParallels =
|
|
FetchStandardParallels( szGridMappingValue );
|
|
|
|
if( papszStdParallels != NULL ) {
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
}
|
|
else {
|
|
//TODO: add support for 'scale_factor_at_projection_origin' variant to standard parallel
|
|
//Probably then need to calc a std parallel equivalent
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"NetCDF driver does not support import of CF-1 LCEA "
|
|
"'scale_factor_at_projection_origin' variant yet.\n" );
|
|
}
|
|
|
|
dfCentralMeridian =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LONG_CENTRAL_MERIDIAN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetCEA( dfStdP1, dfCentralMeridian,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
CSLDestroy( papszStdParallels );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* lambert_azimuthal_equal_area */
|
|
/* -------------------------------------------------------------------- */
|
|
else if( EQUAL( pszValue, CF_PT_LAEA ) ) {
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
oSRS.SetProjCS( "LAEA (WGS84) " );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetLAEA( dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Azimuthal Equidistant */
|
|
/* -------------------------------------------------------------------- */
|
|
else if( EQUAL( pszValue, CF_PT_AE ) ) {
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetAE( dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Lambert conformal conic */
|
|
/* -------------------------------------------------------------------- */
|
|
else if( EQUAL( pszValue, CF_PT_LCC ) ) {
|
|
|
|
char **papszStdParallels = NULL;
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LONG_CENTRAL_MERIDIAN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
papszStdParallels =
|
|
FetchStandardParallels( szGridMappingValue );
|
|
|
|
/* 2SP variant */
|
|
if( CSLCount( papszStdParallels ) == 2 ) {
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
dfStdP2 = CPLAtofM( papszStdParallels[1] );
|
|
oSRS.SetLCC( dfStdP1, dfStdP2, dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
/* 1SP variant (with standard_parallel or center lon) */
|
|
/* See comments in netcdfdataset.h for this projection. */
|
|
else {
|
|
|
|
dfScale =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SCALE_FACTOR_ORIGIN, -1.0 );
|
|
|
|
/* CF definition, without scale factor */
|
|
if( CPLIsEqual(dfScale, -1.0) ) {
|
|
|
|
/* with standard_parallel */
|
|
if( CSLCount( papszStdParallels ) == 1 )
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
/* with center lon instead */
|
|
else
|
|
dfStdP1 = dfCenterLat;
|
|
dfStdP2 = dfStdP1;
|
|
|
|
/* test if we should actually compute scale factor */
|
|
if ( ! CPLIsEqual( dfStdP1, dfCenterLat ) ) {
|
|
CPLError( CE_Warning, CPLE_NotSupported,
|
|
"NetCDF driver import of LCC-1SP with standard_parallel1 != latitude_of_projection_origin\n"
|
|
"(which forces a computation of scale_factor) is experimental (bug #3324)\n" );
|
|
/* use Snyder eq. 15-4 to compute dfScale from dfStdP1 and dfCenterLat */
|
|
/* only tested for dfStdP1=dfCenterLat and (25,26), needs more data for testing */
|
|
/* other option: use the 2SP variant - how to compute new standard parallels? */
|
|
dfScale = ( cos(dfStdP1) * pow( tan(NCDF_PI/4 + dfStdP1/2), sin(dfStdP1) ) ) /
|
|
( cos(dfCenterLat) * pow( tan(NCDF_PI/4 + dfCenterLat/2), sin(dfCenterLat) ) );
|
|
}
|
|
/* default is 1.0 */
|
|
else
|
|
dfScale = 1.0;
|
|
|
|
oSRS.SetLCC1SP( dfCenterLat, dfCenterLon, dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
/* store dfStdP1 so we can output it to CF later */
|
|
oSRS.SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
|
|
}
|
|
/* OGC/PROJ.4 definition with scale factor */
|
|
else {
|
|
oSRS.SetLCC1SP( dfCenterLat, dfCenterLon, dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
}
|
|
|
|
|
|
bGotCfSRS = TRUE;
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
CSLDestroy( papszStdParallels );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Is this Latitude/Longitude Grid explicitly */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
else if ( EQUAL ( pszValue, CF_PT_LATITUDE_LONGITUDE ) ) {
|
|
bGotCfSRS = TRUE;
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Mercator */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
else if ( EQUAL ( pszValue, CF_PT_MERCATOR ) ) {
|
|
|
|
char **papszStdParallels = NULL;
|
|
|
|
/* If there is a standard_parallel, know it is Mercator 2SP */
|
|
papszStdParallels =
|
|
FetchStandardParallels( szGridMappingValue );
|
|
|
|
if (NULL != papszStdParallels) {
|
|
/* CF-1 Mercator 2SP always has lat centered at equator */
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
|
|
dfCenterLat = 0.0;
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
oSRS.SetMercator2SP( dfStdP1, dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
else {
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfScale =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SCALE_FACTOR_ORIGIN,
|
|
1.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
oSRS.SetMercator( dfCenterLat, dfCenterLon, dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
}
|
|
|
|
bGotCfSRS = TRUE;
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
CSLDestroy( papszStdParallels );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Orthographic */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
else if ( EQUAL ( pszValue, CF_PT_ORTHOGRAPHIC ) ) {
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
|
|
oSRS.SetOrthographic( dfCenterLat, dfCenterLon,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Polar Stereographic */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
else if ( EQUAL ( pszValue, CF_PT_POLAR_STEREO ) ) {
|
|
|
|
char **papszStdParallels = NULL;
|
|
|
|
dfScale =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SCALE_FACTOR_ORIGIN,
|
|
-1.0 );
|
|
|
|
papszStdParallels =
|
|
FetchStandardParallels( szGridMappingValue );
|
|
|
|
/* CF allows the use of standard_parallel (lat_ts) OR scale_factor (k0),
|
|
make sure we have standard_parallel, using Snyder eq. 22-7
|
|
with k=1 and lat=standard_parallel */
|
|
if ( papszStdParallels != NULL ) {
|
|
dfStdP1 = CPLAtofM( papszStdParallels[0] );
|
|
/* compute scale_factor from standard_parallel */
|
|
/* this creates WKT that is inconsistent, don't write for now
|
|
also proj4 does not seem to use this parameter */
|
|
// dfScale = ( 1.0 + fabs( sin( dfStdP1 * NCDF_PI / 180.0 ) ) ) / 2.0;
|
|
}
|
|
else {
|
|
if ( ! CPLIsEqual(dfScale,-1.0) ) {
|
|
/* compute standard_parallel from scale_factor */
|
|
dfStdP1 = asin( 2*dfScale - 1 ) * 180.0 / NCDF_PI;
|
|
|
|
/* fetch latitude_of_projection_origin (+90/-90)
|
|
used here for the sign of standard_parallel */
|
|
double dfLatProjOrigin =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN,
|
|
0.0 );
|
|
if ( ! CPLIsEqual(dfLatProjOrigin,90.0) &&
|
|
! CPLIsEqual(dfLatProjOrigin,-90.0) ) {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"Polar Stereographic must have a %s parameter equal to +90 or -90\n.",
|
|
CF_PP_LAT_PROJ_ORIGIN );
|
|
dfLatProjOrigin = 90.0;
|
|
}
|
|
if ( CPLIsEqual(dfLatProjOrigin,-90.0) )
|
|
dfStdP1 = - dfStdP1;
|
|
}
|
|
else {
|
|
dfStdP1 = 0.0; //just to avoid warning at compilation
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The NetCDF driver does not support import of CF-1 Polar stereographic "
|
|
"without standard_parallel and scale_factor_at_projection_origin parameters.\n" );
|
|
}
|
|
}
|
|
|
|
/* set scale to default value 1.0 if it was not set */
|
|
if ( CPLIsEqual(dfScale,-1.0) )
|
|
dfScale = 1.0;
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_VERT_LONG_FROM_POLE, 0.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
/* map CF CF_PP_STD_PARALLEL_1 to WKT SRS_PP_LATITUDE_OF_ORIGIN */
|
|
oSRS.SetPS( dfStdP1, dfCenterLon, dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
CSLDestroy( papszStdParallels );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Stereographic */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
else if ( EQUAL ( pszValue, CF_PT_STEREO ) ) {
|
|
|
|
dfCenterLon =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LON_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfCenterLat =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_LAT_PROJ_ORIGIN, 0.0 );
|
|
|
|
dfScale =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_SCALE_FACTOR_ORIGIN,
|
|
1.0 );
|
|
|
|
dfFalseEasting =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_EASTING, 0.0 );
|
|
|
|
dfFalseNorthing =
|
|
poDS->FetchCopyParm( szGridMappingValue,
|
|
CF_PP_FALSE_NORTHING, 0.0 );
|
|
|
|
bGotCfSRS = TRUE;
|
|
oSRS.SetStereographic( dfCenterLat, dfCenterLon, dfScale,
|
|
dfFalseEasting, dfFalseNorthing );
|
|
|
|
if( !bGotGeogCS )
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Is this Latitude/Longitude Grid, default */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
} else if( EQUAL( szDimNameX,"lon" ) ) {
|
|
oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
|
|
} else {
|
|
// This would be too indiscrimant. But we should set
|
|
// it if we know the data is geographic.
|
|
//oSRS.SetWellKnownGeogCS( "WGS84" );
|
|
}
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read projection coordinates */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
nc_inq_varid( cdfid, poDS->papszDimName[nXDimID], &nVarDimXID );
|
|
nc_inq_varid( cdfid, poDS->papszDimName[nYDimID], &nVarDimYID );
|
|
|
|
if( ( nVarDimXID != -1 ) && ( nVarDimYID != -1 ) ) {
|
|
pdfXCoord = (double *) CPLCalloc( xdim, sizeof(double) );
|
|
pdfYCoord = (double *) CPLCalloc( ydim, sizeof(double) );
|
|
|
|
start[0] = 0;
|
|
edge[0] = xdim;
|
|
status = nc_get_vara_double( cdfid, nVarDimXID,
|
|
start, edge, pdfXCoord);
|
|
|
|
edge[0] = ydim;
|
|
status = nc_get_vara_double( cdfid, nVarDimYID,
|
|
start, edge, pdfYCoord);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for bottom-up from the Y-axis order */
|
|
/* see bugs #4284 and #4251 */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if ( pdfYCoord[0] > pdfYCoord[1] )
|
|
poDS->bBottomUp = FALSE;
|
|
else
|
|
poDS->bBottomUp = TRUE;
|
|
|
|
CPLDebug( "GDAL_netCDF", "set bBottomUp = %d from Y axis", poDS->bBottomUp );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* convert ]180,360] longitude values to [-180,180] */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if ( NCDFIsVarLongitude( cdfid, nVarDimXID, NULL ) &&
|
|
CSLTestBoolean(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) ) {
|
|
/* if mininum longitude is > 180, subtract 360 from all */
|
|
if ( MIN( pdfXCoord[0], pdfXCoord[xdim-1] ) > 180.0 ) {
|
|
for ( size_t i=0; i<xdim ; i++ )
|
|
pdfXCoord[i] -= 360;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set Projection from CF */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( bGotGeogCS || bGotCfSRS ) {
|
|
/* Set SRS Units */
|
|
|
|
/* check units for x and y */
|
|
if( oSRS.IsProjected( ) ) {
|
|
const char *pszUnitsX = NULL;
|
|
const char *pszUnitsY = NULL;
|
|
|
|
strcpy( szTemp, poDS->papszDimName[nXDimID] );
|
|
strcat( szTemp, "#units" );
|
|
pszValue = CSLFetchNameValue( poDS->papszMetadata,
|
|
szTemp );
|
|
if( pszValue != NULL )
|
|
pszUnitsX = pszValue;
|
|
|
|
strcpy( szTemp, poDS->papszDimName[nYDimID] );
|
|
strcat( szTemp, "#units" );
|
|
pszValue = CSLFetchNameValue( poDS->papszMetadata,
|
|
szTemp );
|
|
if( pszValue != NULL )
|
|
pszUnitsY = pszValue;
|
|
|
|
/* TODO: what to do if units are not equal in X and Y */
|
|
if ( (pszUnitsX != NULL) && (pszUnitsY != NULL) &&
|
|
EQUAL(pszUnitsX,pszUnitsY) )
|
|
pszUnits = pszUnitsX;
|
|
|
|
/* add units to PROJCS */
|
|
if ( pszUnits != NULL && ! EQUAL(pszUnits,"") ) {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"units=%s", pszUnits );
|
|
if ( EQUAL(pszUnits,"m") ) {
|
|
oSRS.SetLinearUnits( "metre", 1.0 );
|
|
oSRS.SetAuthority( "PROJCS|UNIT", "EPSG", 9001 );
|
|
}
|
|
else if ( EQUAL(pszUnits,"km") ) {
|
|
oSRS.SetLinearUnits( "kilometre", 1000.0 );
|
|
oSRS.SetAuthority( "PROJCS|UNIT", "EPSG", 9036 );
|
|
}
|
|
/* TODO check for other values */
|
|
// else
|
|
// oSRS.SetLinearUnits(pszUnits, 1.0);
|
|
}
|
|
}
|
|
else if ( oSRS.IsGeographic() ) {
|
|
oSRS.SetAngularUnits( CF_UNITS_D, CPLAtof(SRS_UA_DEGREE_CONV) );
|
|
oSRS.SetAuthority( "GEOGCS|UNIT", "EPSG", 9122 );
|
|
}
|
|
|
|
/* Set Projection */
|
|
oSRS.exportToWkt( &(pszTempProjection) );
|
|
CPLDebug( "GDAL_netCDF", "setting WKT from CF" );
|
|
SetProjection( pszTempProjection );
|
|
CPLFree( pszTempProjection );
|
|
|
|
if ( !bGotCfGT )
|
|
CPLDebug( "GDAL_netCDF", "got SRS but no geotransform from CF!");
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Is pixel spacing uniform accross the map? */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check Longitude */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( xdim == 2 ) {
|
|
bLonSpacingOK = TRUE;
|
|
}
|
|
else
|
|
{
|
|
nSpacingBegin = (int) poDS->rint((pdfXCoord[1] - pdfXCoord[0]) * 1000);
|
|
|
|
nSpacingMiddle = (int) poDS->rint((pdfXCoord[xdim/2+1] -
|
|
pdfXCoord[xdim/2]) * 1000);
|
|
|
|
nSpacingLast = (int) poDS->rint((pdfXCoord[xdim-1] -
|
|
pdfXCoord[xdim-2]) * 1000);
|
|
|
|
CPLDebug("GDAL_netCDF",
|
|
"xdim: %ld nSpacingBegin: %d nSpacingMiddle: %d nSpacingLast: %d",
|
|
(long)xdim, nSpacingBegin, nSpacingMiddle, nSpacingLast );
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug("GDAL_netCDF",
|
|
"xcoords: %f %f %f %f %f %f",
|
|
pdfXCoord[0], pdfXCoord[1], pdfXCoord[xdim / 2], pdfXCoord[(xdim / 2) + 1],
|
|
pdfXCoord[xdim - 2], pdfXCoord[xdim-1]);
|
|
#endif
|
|
|
|
if( ( abs( abs( nSpacingBegin ) - abs( nSpacingLast ) ) <= 1 ) &&
|
|
( abs( abs( nSpacingBegin ) - abs( nSpacingMiddle ) ) <= 1 ) &&
|
|
( abs( abs( nSpacingMiddle ) - abs( nSpacingLast ) ) <= 1 ) ) {
|
|
bLonSpacingOK = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( bLonSpacingOK == FALSE ) {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"Longitude is not equally spaced." );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check Latitude */
|
|
/* -------------------------------------------------------------------- */
|
|
if( ydim == 2 ) {
|
|
bLatSpacingOK = TRUE;
|
|
}
|
|
else
|
|
{
|
|
nSpacingBegin = (int) poDS->rint((pdfYCoord[1] - pdfYCoord[0]) *
|
|
1000);
|
|
|
|
nSpacingMiddle = (int) poDS->rint((pdfYCoord[ydim/2+1] -
|
|
pdfYCoord[ydim/2]) *
|
|
1000);
|
|
|
|
nSpacingLast = (int) poDS->rint((pdfYCoord[ydim-1] -
|
|
pdfYCoord[ydim-2]) *
|
|
1000);
|
|
|
|
CPLDebug("GDAL_netCDF",
|
|
"ydim: %ld nSpacingBegin: %d nSpacingMiddle: %d nSpacingLast: %d",
|
|
(long)ydim, nSpacingBegin, nSpacingMiddle, nSpacingLast );
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug("GDAL_netCDF",
|
|
"ycoords: %f %f %f %f %f %f",
|
|
pdfYCoord[0], pdfYCoord[1], pdfYCoord[ydim / 2], pdfYCoord[(ydim / 2) + 1],
|
|
pdfYCoord[ydim - 2], pdfYCoord[ydim-1]);
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* For Latitude we allow an error of 0.1 degrees for gaussian */
|
|
/* gridding (only if this is not a projected SRS) */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( ( abs( abs( nSpacingBegin ) - abs( nSpacingLast ) ) <= 1 ) &&
|
|
( abs( abs( nSpacingBegin ) - abs( nSpacingMiddle ) ) <= 1 ) &&
|
|
( abs( abs( nSpacingMiddle ) - abs( nSpacingLast ) ) <= 1 ) ) {
|
|
bLatSpacingOK = TRUE;
|
|
}
|
|
else if( !oSRS.IsProjected() &&
|
|
( (( abs( abs(nSpacingBegin) - abs(nSpacingLast) ) ) <= 100 ) &&
|
|
(( abs( abs(nSpacingBegin) - abs(nSpacingMiddle) ) ) <= 100 ) &&
|
|
(( abs( abs(nSpacingMiddle) - abs(nSpacingLast) ) ) <= 100 ) ) ) {
|
|
bLatSpacingOK = TRUE;
|
|
CPLError(CE_Warning, 1,"Latitude grid not spaced evenly.\nSeting projection for grid spacing is within 0.1 degrees threshold.\n");
|
|
|
|
CPLDebug("GDAL_netCDF",
|
|
"Latitude grid not spaced evenly, but within 0.1 degree threshold (probably a Gaussian grid).\n"
|
|
"Saving original latitude values in Y_VALUES geolocation metadata" );
|
|
Set1DGeolocation( nVarDimYID, "Y" );
|
|
}
|
|
|
|
if ( bLatSpacingOK == FALSE ) {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"Latitude is not equally spaced." );
|
|
}
|
|
}
|
|
if ( ( bLonSpacingOK == TRUE ) && ( bLatSpacingOK == TRUE ) ) {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We have gridded data so we can set the Gereferencing info. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Enable GeoTransform */
|
|
/* -------------------------------------------------------------------- */
|
|
/* ----------------------------------------------------------*/
|
|
/* In the following "actual_range" and "node_offset" */
|
|
/* are attributes used by netCDF files created by GMT. */
|
|
/* If we find them we know how to proceed. Else, use */
|
|
/* the original algorithm. */
|
|
/* --------------------------------------------------------- */
|
|
double dummy[2], xMinMax[2], yMinMax[2];
|
|
int node_offset = 0;
|
|
|
|
bGotCfGT = TRUE;
|
|
|
|
nc_get_att_int (cdfid, NC_GLOBAL, "node_offset", &node_offset);
|
|
|
|
if (!nc_get_att_double (cdfid, nVarDimXID, "actual_range", dummy)) {
|
|
xMinMax[0] = dummy[0];
|
|
xMinMax[1] = dummy[1];
|
|
}
|
|
else {
|
|
xMinMax[0] = pdfXCoord[0];
|
|
xMinMax[1] = pdfXCoord[xdim-1];
|
|
node_offset = 0;
|
|
}
|
|
|
|
if (!nc_get_att_double (cdfid, nVarDimYID, "actual_range", dummy)) {
|
|
yMinMax[0] = dummy[0];
|
|
yMinMax[1] = dummy[1];
|
|
}
|
|
else {
|
|
yMinMax[0] = pdfYCoord[0];
|
|
yMinMax[1] = pdfYCoord[ydim-1];
|
|
node_offset = 0;
|
|
}
|
|
|
|
/* Check for reverse order of y-coordinate */
|
|
if ( yMinMax[0] > yMinMax[1] ) {
|
|
dummy[0] = yMinMax[1];
|
|
dummy[1] = yMinMax[0];
|
|
yMinMax[0] = dummy[0];
|
|
yMinMax[1] = dummy[1];
|
|
}
|
|
|
|
adfTempGeoTransform[0] = xMinMax[0];
|
|
adfTempGeoTransform[2] = 0;
|
|
adfTempGeoTransform[3] = yMinMax[1];
|
|
adfTempGeoTransform[4] = 0;
|
|
adfTempGeoTransform[1] = ( xMinMax[1] - xMinMax[0] ) /
|
|
( poDS->nRasterXSize + (node_offset - 1) );
|
|
adfTempGeoTransform[5] = ( yMinMax[0] - yMinMax[1] ) /
|
|
( poDS->nRasterYSize + (node_offset - 1) );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute the center of the pixel */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( !node_offset ) { // Otherwise its already the pixel center
|
|
adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
|
|
adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
|
|
}
|
|
|
|
}
|
|
|
|
CPLFree( pdfXCoord );
|
|
CPLFree( pdfYCoord );
|
|
}// end if (has dims)
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Process custom GDAL values (spatial_ref, GeoTransform) */
|
|
/* -------------------------------------------------------------------- */
|
|
if( !EQUAL( szGridMappingValue, "" ) ) {
|
|
|
|
if( pszWKT != NULL ) {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compare SRS obtained from CF attributes and GDAL WKT */
|
|
/* If possible use the more complete GDAL WKT */
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set the SRS to the one written by GDAL */
|
|
if ( ! bGotCfSRS || poDS->pszProjection == NULL || ! bIsGdalCfFile ) {
|
|
bGotGdalSRS = TRUE;
|
|
CPLDebug( "GDAL_netCDF", "setting WKT from GDAL" );
|
|
SetProjection( pszWKT );
|
|
}
|
|
else { /* use the SRS from GDAL if it doesn't conflict with the one from CF */
|
|
char *pszProjectionGDAL = (char*) pszWKT ;
|
|
OGRSpatialReference oSRSGDAL;
|
|
oSRSGDAL.importFromWkt( &pszProjectionGDAL );
|
|
/* set datum to unknown or else datums will not match, see bug #4281 */
|
|
if ( oSRSGDAL.GetAttrNode( "DATUM" ) )
|
|
oSRSGDAL.GetAttrNode( "DATUM" )->GetChild(0)->SetValue( "unknown" );
|
|
/* need this for setprojection autotest */
|
|
if ( oSRSGDAL.GetAttrNode( "PROJCS" ) )
|
|
oSRSGDAL.GetAttrNode( "PROJCS" )->GetChild(0)->SetValue( "unnamed" );
|
|
if ( oSRSGDAL.GetAttrNode( "GEOGCS" ) )
|
|
oSRSGDAL.GetAttrNode( "GEOGCS" )->GetChild(0)->SetValue( "unknown" );
|
|
oSRSGDAL.GetRoot()->StripNodes( "UNIT" );
|
|
if ( oSRS.IsSame(&oSRSGDAL) ) {
|
|
// printf("ARE SAME, using GDAL WKT\n");
|
|
bGotGdalSRS = TRUE;
|
|
CPLDebug( "GDAL_netCDF", "setting WKT from GDAL" );
|
|
SetProjection( pszWKT );
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"got WKT from GDAL \n[%s]\nbut not using it because conflicts with CF\n[%s]\n",
|
|
pszWKT, poDS->pszProjection );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for GeoTransform Array, if not found in CF */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( !bGotCfGT ) {
|
|
|
|
/* TODO read the GT values and detect for conflict with CF */
|
|
/* this could resolve the GT precision loss issue */
|
|
|
|
if( pszGeoTransform != NULL ) {
|
|
|
|
bGotGdalGT = TRUE;
|
|
|
|
papszGeoTransform = CSLTokenizeString2( pszGeoTransform,
|
|
" ",
|
|
CSLT_HONOURSTRINGS );
|
|
adfTempGeoTransform[0] = CPLAtof( papszGeoTransform[0] );
|
|
adfTempGeoTransform[1] = CPLAtof( papszGeoTransform[1] );
|
|
adfTempGeoTransform[2] = CPLAtof( papszGeoTransform[2] );
|
|
adfTempGeoTransform[3] = CPLAtof( papszGeoTransform[3] );
|
|
adfTempGeoTransform[4] = CPLAtof( papszGeoTransform[4] );
|
|
adfTempGeoTransform[5] = CPLAtof( papszGeoTransform[5] );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for corner array values */
|
|
/* -------------------------------------------------------------------- */
|
|
} else {
|
|
double dfNN=0.0, dfSN=0.0, dfEE=0.0, dfWE=0.0;
|
|
int bGotNN=FALSE, bGotSN=FALSE, bGotEE=FALSE, bGotWE=FALSE;
|
|
// CPLDebug( "GDAL_netCDF", "looking for geotransform corners\n" );
|
|
|
|
strcpy(szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, "Northernmost_Northing");
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
if( pszValue != NULL ) {
|
|
dfNN = CPLAtof( pszValue );
|
|
bGotNN = TRUE;
|
|
}
|
|
|
|
strcpy(szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, "Southernmost_Northing");
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
if( pszValue != NULL ) {
|
|
dfSN = CPLAtof( pszValue );
|
|
bGotSN = TRUE;
|
|
}
|
|
|
|
strcpy(szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, "Easternmost_Easting");
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
if( pszValue != NULL ) {
|
|
dfEE = CPLAtof( pszValue );
|
|
bGotEE = TRUE;
|
|
}
|
|
|
|
strcpy(szTemp,szGridMappingValue);
|
|
strcat( szTemp, "#" );
|
|
strcat( szTemp, "Westernmost_Easting");
|
|
pszValue = CSLFetchNameValue(poDS->papszMetadata, szTemp);
|
|
if( pszValue != NULL ) {
|
|
dfWE = CPLAtof( pszValue );
|
|
bGotWE = TRUE;
|
|
}
|
|
|
|
/* Only set the GeoTransform if we got all the values */
|
|
if ( bGotNN && bGotSN && bGotEE && bGotWE ) {
|
|
|
|
bGotGdalGT = TRUE;
|
|
|
|
adfTempGeoTransform[0] = dfWE;
|
|
adfTempGeoTransform[1] = (dfEE - dfWE) /
|
|
( poDS->GetRasterXSize() - 1 );
|
|
adfTempGeoTransform[2] = 0.0;
|
|
adfTempGeoTransform[3] = dfNN;
|
|
adfTempGeoTransform[4] = 0.0;
|
|
adfTempGeoTransform[5] = (dfSN - dfNN) /
|
|
( poDS->GetRasterYSize() - 1 );
|
|
/* compute the center of the pixel */
|
|
adfTempGeoTransform[0] = dfWE
|
|
- (adfTempGeoTransform[1] / 2);
|
|
adfTempGeoTransform[3] = dfNN
|
|
- (adfTempGeoTransform[5] / 2);
|
|
}
|
|
} // (pszGeoTransform != NULL)
|
|
CSLDestroy( papszGeoTransform );
|
|
|
|
if ( bGotGdalSRS && ! bGotGdalGT )
|
|
CPLDebug( "GDAL_netCDF", "got SRS but not geotransform from GDAL!");
|
|
|
|
} // if ( !bGotCfGT )
|
|
|
|
}
|
|
}
|
|
|
|
/* Set GeoTransform if we got a complete one - after projection has been set */
|
|
if ( bGotCfGT || bGotGdalGT ) {
|
|
SetGeoTransform( adfTempGeoTransform );
|
|
}
|
|
|
|
/* Process geolocation arrays from CF "coordinates" attribute */
|
|
/* perhaps we should only add if is not a (supported) CF projection (bIsCfProjection */
|
|
ProcessCFGeolocation( nVarId );
|
|
|
|
/* debuging reports */
|
|
CPLDebug( "GDAL_netCDF",
|
|
"bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotGdalSRS=%d bGotGdalGT=%d",
|
|
bGotGeogCS, bGotCfSRS, bGotCfGT, bGotGdalSRS, bGotGdalGT );
|
|
|
|
if ( !bGotCfGT && !bGotGdalGT )
|
|
CPLDebug( "GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
|
|
|
|
if ( !bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT)
|
|
CPLDebug( "GDAL_netCDF", "did not get projection from CF nor GDAL!");
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Search for Well-known GeogCS if got only CF WKT */
|
|
/* Disabled for now, as a named datum also include control points */
|
|
/* (see mailing list and bug#4281 */
|
|
/* For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py */
|
|
/* -------------------------------------------------------------------- */
|
|
/* disabled for now, but could be set in a config option */
|
|
bLookForWellKnownGCS = FALSE;
|
|
if ( bLookForWellKnownGCS && bGotCfSRS && ! bGotGdalSRS ) {
|
|
/* ET - could use a more exhaustive method by scanning all EPSG codes in data/gcs.csv */
|
|
/* as proposed by Even in the gdal-dev mailing list "help for comparing two WKT" */
|
|
/* this code could be contributed to a new function */
|
|
/* OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS( const OGRSpatialReference *poOther ) */
|
|
CPLDebug( "GDAL_netCDF", "Searching for Well-known GeogCS" );
|
|
const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
|
|
char *pszWKGCS = NULL;
|
|
oSRS.exportToPrettyWkt( &pszWKGCS );
|
|
for( size_t i=0; i<sizeof(pszWKGCSList)/8; i++ ) {
|
|
pszWKGCS = CPLStrdup( pszWKGCSList[i] );
|
|
OGRSpatialReference oSRSTmp;
|
|
oSRSTmp.SetWellKnownGeogCS( pszWKGCSList[i] );
|
|
/* set datum to unknown, bug #4281 */
|
|
if ( oSRSTmp.GetAttrNode( "DATUM" ) )
|
|
oSRSTmp.GetAttrNode( "DATUM" )->GetChild(0)->SetValue( "unknown" );
|
|
/* could use OGRSpatialReference::StripCTParms() but let's keep TOWGS84 */
|
|
oSRSTmp.GetRoot()->StripNodes( "AXIS" );
|
|
oSRSTmp.GetRoot()->StripNodes( "AUTHORITY" );
|
|
oSRSTmp.GetRoot()->StripNodes( "EXTENSION" );
|
|
|
|
oSRSTmp.exportToPrettyWkt( &pszWKGCS );
|
|
if ( oSRS.IsSameGeogCS(&oSRSTmp) ) {
|
|
oSRS.SetWellKnownGeogCS( pszWKGCSList[i] );
|
|
oSRS.exportToWkt( &(pszTempProjection) );
|
|
SetProjection( pszTempProjection );
|
|
CPLFree( pszTempProjection );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int netCDFDataset::ProcessCFGeolocation( int nVarId )
|
|
{
|
|
int bAddGeoloc = FALSE;
|
|
char *pszTemp = NULL;
|
|
char **papszTokens = NULL;
|
|
CPLString osTMP;
|
|
char szGeolocXName[NC_MAX_NAME];
|
|
char szGeolocYName[NC_MAX_NAME];
|
|
szGeolocXName[0] = '\0';
|
|
szGeolocYName[0] = '\0';
|
|
|
|
if ( NCDFGetAttr( cdfid, nVarId, "coordinates", &pszTemp ) == CE_None ) {
|
|
/* get X and Y geolocation names from coordinates attribute */
|
|
papszTokens = CSLTokenizeString2( pszTemp, " ", 0 );
|
|
if ( CSLCount(papszTokens) >= 2 ) {
|
|
/* test that each variable is longitude/latitude */
|
|
for ( int i=0; i<CSLCount(papszTokens); i++ ) {
|
|
if ( NCDFIsVarLongitude(cdfid, -1, papszTokens[i]) )
|
|
strcpy( szGeolocXName, papszTokens[i] );
|
|
else if ( NCDFIsVarLatitude(cdfid, -1, papszTokens[i]) )
|
|
strcpy( szGeolocYName, papszTokens[i] );
|
|
}
|
|
/* add GEOLOCATION metadata */
|
|
if ( !EQUAL(szGeolocXName,"") && !EQUAL(szGeolocYName,"") ) {
|
|
bAddGeoloc = TRUE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"using variables %s and %s for GEOLOCATION",
|
|
szGeolocXName, szGeolocYName );
|
|
|
|
SetMetadataItem( "SRS", SRS_WKT_WGS84, "GEOLOCATION" );
|
|
|
|
osTMP.Printf( "NETCDF:\"%s\":%s",
|
|
osFilename.c_str(), szGeolocXName );
|
|
SetMetadataItem( "X_DATASET", osTMP, "GEOLOCATION" );
|
|
SetMetadataItem( "X_BAND", "1" , "GEOLOCATION" );
|
|
osTMP.Printf( "NETCDF:\"%s\":%s",
|
|
osFilename.c_str(), szGeolocYName );
|
|
SetMetadataItem( "Y_DATASET", osTMP, "GEOLOCATION" );
|
|
SetMetadataItem( "Y_BAND", "1" , "GEOLOCATION" );
|
|
|
|
SetMetadataItem( "PIXEL_OFFSET", "0", "GEOLOCATION" );
|
|
SetMetadataItem( "PIXEL_STEP", "1", "GEOLOCATION" );
|
|
|
|
SetMetadataItem( "LINE_OFFSET", "0", "GEOLOCATION" );
|
|
SetMetadataItem( "LINE_STEP", "1", "GEOLOCATION" );
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"coordinates attribute [%s] is unsupported",
|
|
pszTemp );
|
|
}
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"coordinates attribute [%s] with %d element(s) is unsupported",
|
|
pszTemp, CSLCount(papszTokens) );
|
|
}
|
|
if (papszTokens) CSLDestroy(papszTokens);
|
|
CPLFree( pszTemp );
|
|
}
|
|
|
|
return bAddGeoloc;
|
|
}
|
|
|
|
CPLErr netCDFDataset::Set1DGeolocation( int nVarId, const char *szDimName )
|
|
{
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
char *pszVarValues = NULL;
|
|
CPLErr eErr;
|
|
|
|
/* get values */
|
|
eErr = NCDFGet1DVar( cdfid, nVarId, &pszVarValues );
|
|
if ( eErr != CE_None )
|
|
return eErr;
|
|
|
|
/* write metadata */
|
|
sprintf( szTemp, "%s_VALUES", szDimName );
|
|
SetMetadataItem( szTemp, pszVarValues, "GEOLOCATION2" );
|
|
|
|
CPLFree( pszVarValues );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
|
|
double *netCDFDataset::Get1DGeolocation( CPL_UNUSED const char *szDimName, int &nVarLen )
|
|
{
|
|
char **papszValues = NULL;
|
|
char *pszTemp = NULL;
|
|
double *pdfVarValues = NULL;
|
|
|
|
nVarLen = 0;
|
|
|
|
/* get Y_VALUES as tokens */
|
|
papszValues = NCDFTokenizeArray( GetMetadataItem( "Y_VALUES", "GEOLOCATION2" ) );
|
|
if ( papszValues == NULL )
|
|
return NULL;
|
|
|
|
/* initialize and fill array */
|
|
nVarLen = CSLCount(papszValues);
|
|
pdfVarValues = (double *) CPLCalloc( nVarLen, sizeof( double ) );
|
|
for(int i=0, j=0; i < nVarLen; i++) {
|
|
if ( ! bBottomUp ) j=nVarLen - 1 - i;
|
|
else j=i; /* invert latitude values */
|
|
pdfVarValues[j] = CPLStrtod( papszValues[i], &pszTemp );
|
|
}
|
|
CSLDestroy( papszValues );
|
|
|
|
return pdfVarValues;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* SetProjection() */
|
|
/************************************************************************/
|
|
CPLErr netCDFDataset::SetProjection( const char * pszNewProjection )
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
/* TODO look if proj. already defined, like in geotiff */
|
|
if( pszNewProjection == NULL )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined, "NULL projection." );
|
|
return CE_Failure;
|
|
}
|
|
|
|
if( bSetProjection && (GetAccess() == GA_Update) )
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"netCDFDataset::SetProjection() should only be called once "
|
|
"in update mode!\npszNewProjection=\n%s",
|
|
pszNewProjection );
|
|
}
|
|
|
|
CPLDebug( "GDAL_netCDF", "SetProjection, WKT = %s", pszNewProjection );
|
|
|
|
if( !EQUALN(pszNewProjection,"GEOGCS",6)
|
|
&& !EQUALN(pszNewProjection,"PROJCS",6)
|
|
&& !EQUAL(pszNewProjection,"") )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Only OGC WKT GEOGCS and PROJCS Projections supported for writing to NetCDF.\n"
|
|
"%s not supported.",
|
|
pszNewProjection );
|
|
|
|
return CE_Failure;
|
|
}
|
|
|
|
CPLFree( pszProjection );
|
|
pszProjection = CPLStrdup( pszNewProjection );
|
|
|
|
if( GetAccess() == GA_Update )
|
|
{
|
|
if ( bSetGeoTransform && ! bSetProjection ) {
|
|
bSetProjection = TRUE;
|
|
return AddProjectionVars();
|
|
}
|
|
}
|
|
|
|
bSetProjection = TRUE;
|
|
|
|
return CE_None;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFDataset::SetGeoTransform ( double * padfTransform )
|
|
{
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
memcpy( adfGeoTransform, padfTransform, sizeof(double)*6 );
|
|
// bGeoTransformValid = TRUE;
|
|
// bGeoTIFFInfoChanged = TRUE;
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"SetGeoTransform(%f,%f,%f,%f,%f,%f)",
|
|
padfTransform[0],padfTransform[1],padfTransform[2],
|
|
padfTransform[3],padfTransform[4],padfTransform[5]);
|
|
|
|
if( GetAccess() == GA_Update )
|
|
{
|
|
if ( bSetProjection && ! bSetGeoTransform ) {
|
|
bSetGeoTransform = TRUE;
|
|
return AddProjectionVars();
|
|
}
|
|
}
|
|
|
|
bSetGeoTransform = TRUE;
|
|
|
|
return CE_None;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* AddProjectionVars() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFDataset::AddProjectionVars( GDALProgressFunc pfnProgress,
|
|
void * pProgressData )
|
|
{
|
|
OGRSpatialReference oSRS;
|
|
int NCDFVarID = -1;
|
|
double dfTemp = 0.0;
|
|
const char *pszValue = NULL;
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
CPLErr eErr = CE_None;
|
|
|
|
char szGeoTransform[ NCDF_MAX_STR_LEN ];
|
|
*szGeoTransform = '\0';
|
|
char *pszWKT = NULL;
|
|
const char *pszUnits = NULL;
|
|
char szUnits[ NCDF_MAX_STR_LEN ];
|
|
szUnits[0]='\0';
|
|
|
|
int bWriteGridMapping = FALSE;
|
|
int bWriteLonLat = FALSE;
|
|
int bHasGeoloc = FALSE;
|
|
int bWriteGDALTags = FALSE;
|
|
int bWriteGeoTransform = FALSE;
|
|
|
|
nc_type eLonLatType = NC_NAT;
|
|
int nVarLonID=-1, nVarLatID=-1;
|
|
int nVarXID=-1, nVarYID=-1;
|
|
|
|
/* For GEOLOCATION information */
|
|
char ** papszGeolocationInfo = NULL;
|
|
const char *pszDSName = NULL;
|
|
GDALDatasetH hDS_X = NULL;
|
|
GDALRasterBandH hBand_X = NULL;
|
|
GDALDatasetH hDS_Y = NULL;
|
|
GDALRasterBandH hBand_Y = NULL;
|
|
int nBand;
|
|
|
|
bAddedProjectionVars = TRUE;
|
|
|
|
pszWKT = (char *) pszProjection;
|
|
oSRS.importFromWkt( &pszWKT );
|
|
|
|
if( oSRS.IsProjected() )
|
|
bIsProjected = TRUE;
|
|
else if( oSRS.IsGeographic() )
|
|
bIsGeographic = TRUE;
|
|
|
|
CPLDebug( "GDAL_netCDF", "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
|
|
pszProjection,bIsProjected,bIsGeographic );
|
|
|
|
if ( ! bSetGeoTransform )
|
|
CPLDebug( "GDAL_netCDF", "netCDFDataset::AddProjectionVars() called, "
|
|
"but GeoTransform has not yet been defined!" );
|
|
|
|
if ( ! bSetProjection )
|
|
CPLDebug( "GDAL_netCDF", "netCDFDataset::AddProjectionVars() called, "
|
|
"but Projection has not yet been defined!" );
|
|
|
|
/* check GEOLOCATION information */
|
|
papszGeolocationInfo = GetMetadata("GEOLOCATION");
|
|
if ( papszGeolocationInfo != NULL ) {
|
|
|
|
/* look for geolocation datasets */
|
|
pszDSName = CSLFetchNameValue( papszGeolocationInfo, "X_DATASET" );
|
|
if( pszDSName != NULL )
|
|
hDS_X = GDALOpenShared( pszDSName, GA_ReadOnly );
|
|
pszDSName = CSLFetchNameValue( papszGeolocationInfo, "Y_DATASET" );
|
|
if( pszDSName != NULL )
|
|
hDS_Y = GDALOpenShared( pszDSName, GA_ReadOnly );
|
|
|
|
if ( hDS_X != NULL && hDS_Y != NULL ) {
|
|
nBand = MAX(1,atoi(CSLFetchNameValue( papszGeolocationInfo, "X_BAND" )));
|
|
hBand_X = GDALGetRasterBand( hDS_X, nBand );
|
|
nBand = MAX(1,atoi(CSLFetchNameValue( papszGeolocationInfo, "Y_BAND" )));
|
|
hBand_Y = GDALGetRasterBand( hDS_Y, nBand );
|
|
|
|
/* if geoloc bands are found do basic vaidation based on their dimensions */
|
|
if ( hDS_X != NULL && hDS_Y != NULL ) {
|
|
|
|
int nXSize_XBand = GDALGetRasterXSize( hDS_X );
|
|
int nYSize_XBand = GDALGetRasterYSize( hDS_X );
|
|
int nXSize_YBand = GDALGetRasterXSize( hDS_Y );
|
|
int nYSize_YBand = GDALGetRasterYSize( hDS_Y );
|
|
|
|
/* TODO 1D geolocation arrays not implemented */
|
|
if ( (nYSize_XBand == 1) && (nYSize_YBand == 1) ) {
|
|
bHasGeoloc = FALSE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"1D GEOLOCATION arrays not supported yet" );
|
|
}
|
|
/* 2D bands must have same sizes as the raster bands */
|
|
else if ( (nXSize_XBand != nRasterXSize) ||
|
|
(nYSize_XBand != nRasterYSize) ||
|
|
(nXSize_YBand != nRasterXSize) ||
|
|
(nYSize_YBand != nRasterYSize) ) {
|
|
bHasGeoloc = FALSE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"GEOLOCATION array sizes (%dx%d %dx%d) differ from raster (%dx%d), not supported",
|
|
nXSize_XBand, nYSize_XBand, nXSize_YBand, nYSize_YBand,
|
|
nRasterXSize, nRasterYSize );
|
|
}
|
|
/* 2D bands are only supported for projected SRS (see CF 5.6) */
|
|
else if ( ! bIsProjected ) {
|
|
bHasGeoloc = FALSE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"2D GEOLOCATION arrays only supported for projected SRS" );
|
|
}
|
|
else {
|
|
bHasGeoloc = TRUE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"dataset has GEOLOCATION information, will try to write it" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* process projection options */
|
|
if( bIsProjected )
|
|
{
|
|
int bIsCfProjection = NCDFIsCfProjection( oSRS.GetAttrValue( "PROJECTION" ) );
|
|
bWriteGridMapping = TRUE;
|
|
bWriteGDALTags = CSLFetchBoolean( papszCreationOptions, "WRITE_GDAL_TAGS", TRUE );
|
|
/* force WRITE_GDAL_TAGS if is not a CF projection */
|
|
if ( ! bWriteGDALTags && ! bIsCfProjection )
|
|
bWriteGDALTags = TRUE;
|
|
if ( bWriteGDALTags )
|
|
bWriteGeoTransform = TRUE;
|
|
|
|
/* write lon/lat : default is NO, except if has geolocation */
|
|
/* with IF_NEEDED : write if has geoloc or is not CF projection */
|
|
pszValue = CSLFetchNameValue( papszCreationOptions,"WRITE_LONLAT" );
|
|
if ( pszValue ) {
|
|
if ( EQUAL( pszValue, "IF_NEEDED" ) ) {
|
|
if ( bHasGeoloc || ! bIsCfProjection )
|
|
bWriteLonLat = TRUE;
|
|
else
|
|
bWriteLonLat = FALSE;
|
|
}
|
|
else bWriteLonLat = CSLTestBoolean( pszValue );
|
|
}
|
|
else
|
|
bWriteLonLat = bHasGeoloc;
|
|
|
|
/* save value of pszCFCoordinates for later */
|
|
if ( bWriteLonLat == TRUE ) {
|
|
pszCFCoordinates = CPLStrdup( NCDF_LONLAT );
|
|
}
|
|
|
|
eLonLatType = NC_FLOAT;
|
|
pszValue = CSLFetchNameValueDef(papszCreationOptions,"TYPE_LONLAT", "FLOAT");
|
|
if ( EQUAL(pszValue, "DOUBLE" ) )
|
|
eLonLatType = NC_DOUBLE;
|
|
}
|
|
else
|
|
{
|
|
/* files without a Datum will not have a grid_mapping variable and geographic information */
|
|
if ( bIsGeographic ) bWriteGridMapping = TRUE;
|
|
else bWriteGridMapping = FALSE;
|
|
bWriteGDALTags = CSLFetchBoolean( papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping );
|
|
if ( bWriteGDALTags )
|
|
bWriteGeoTransform = TRUE;
|
|
|
|
pszValue = CSLFetchNameValueDef(papszCreationOptions,"WRITE_LONLAT", "YES");
|
|
if ( EQUAL( pszValue, "IF_NEEDED" ) )
|
|
bWriteLonLat = TRUE;
|
|
else bWriteLonLat = CSLTestBoolean( pszValue );
|
|
/* Don't write lon/lat if no source geotransform */
|
|
if ( ! bSetGeoTransform )
|
|
bWriteLonLat = FALSE;
|
|
/* If we don't write lon/lat, set dimnames to X/Y and write gdal tags*/
|
|
if ( ! bWriteLonLat ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"creating geographic file without lon/lat values!");
|
|
if ( bSetGeoTransform ) {
|
|
bWriteGDALTags = TRUE; //not desireable if no geotransform
|
|
bWriteGeoTransform = TRUE;
|
|
}
|
|
}
|
|
|
|
eLonLatType = NC_DOUBLE;
|
|
pszValue = CSLFetchNameValueDef(papszCreationOptions,"TYPE_LONLAT", "DOUBLE");
|
|
if ( EQUAL(pszValue, "FLOAT" ) )
|
|
eLonLatType = NC_FLOAT;
|
|
}
|
|
|
|
/* make sure we write grid_mapping if we need to write GDAL tags */
|
|
if ( bWriteGDALTags ) bWriteGridMapping = TRUE;
|
|
|
|
/* bottom-up value: new driver is bottom-up by default */
|
|
/* override with WRITE_BOTTOMUP */
|
|
bBottomUp = CSLFetchBoolean( papszCreationOptions, "WRITE_BOTTOMUP", TRUE );
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
|
|
"bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
|
|
bIsProjected,bIsGeographic,bWriteGridMapping,
|
|
bWriteGDALTags,bWriteLonLat,bBottomUp,bHasGeoloc );
|
|
|
|
/* exit if nothing to do */
|
|
if ( !bIsProjected && !bWriteLonLat )
|
|
return CE_None;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Define dimension names */
|
|
/* -------------------------------------------------------------------- */
|
|
/* make sure we are in define mode */
|
|
SetDefineMode( TRUE );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Rename dimensions if lon/lat */
|
|
/* -------------------------------------------------------------------- */
|
|
if( ! bIsProjected )
|
|
{
|
|
/* rename dims to lat/lon */
|
|
papszDimName.Clear(); //if we add other dims one day, this has to change
|
|
papszDimName.AddString( NCDF_DIMNAME_LAT );
|
|
papszDimName.AddString( NCDF_DIMNAME_LON );
|
|
|
|
status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT );
|
|
NCDF_ERR(status);
|
|
status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON );
|
|
NCDF_ERR(status);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write projection attributes */
|
|
/* -------------------------------------------------------------------- */
|
|
if( bWriteGridMapping == TRUE )
|
|
{
|
|
|
|
if( bIsProjected )
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write CF-1.5 compliant Projected attributes */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
const OGR_SRSNode *poPROJCS = oSRS.GetAttrNode( "PROJCS" );
|
|
const char *pszProjName;
|
|
pszProjName = oSRS.GetAttrValue( "PROJECTION" );
|
|
|
|
/* Basic Projection info (grid_mapping and datum) */
|
|
for( int i=0; poNetcdfSRS_PT[i].WKT_SRS != NULL; i++ ) {
|
|
if( EQUAL( poNetcdfSRS_PT[i].WKT_SRS, pszProjName ) ) {
|
|
CPLDebug( "GDAL_netCDF", "GDAL PROJECTION = %s , NCDF PROJECTION = %s",
|
|
poNetcdfSRS_PT[i].WKT_SRS,
|
|
poNetcdfSRS_PT[i].CF_SRS);
|
|
pszCFProjection = CPLStrdup( poNetcdfSRS_PT[i].CF_SRS );
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d)",
|
|
cdfid, poNetcdfSRS_PT[i].CF_SRS, NC_CHAR );
|
|
status = nc_def_var( cdfid,
|
|
poNetcdfSRS_PT[i].CF_SRS,
|
|
NC_CHAR,
|
|
0, NULL, &NCDFVarID );
|
|
NCDF_ERR(status);
|
|
break;
|
|
}
|
|
}
|
|
status = nc_put_att_text( cdfid, NCDFVarID, CF_GRD_MAPPING_NAME,
|
|
strlen( pszCFProjection ),
|
|
pszCFProjection );
|
|
NCDF_ERR(status);
|
|
|
|
/* Various projection attributes */
|
|
// PDS: keep in synch with SetProjection function
|
|
NCDFWriteProjAttribs(poPROJCS, pszProjName, cdfid, NCDFVarID);
|
|
|
|
}
|
|
else
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write CF-1.5 compliant Geographics attributes */
|
|
/* Note: WKT information will not be preserved (e.g. WGS84) */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
pszCFProjection = CPLStrdup( "crs" );
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d)",
|
|
cdfid, pszCFProjection, NC_CHAR );
|
|
status = nc_def_var( cdfid, pszCFProjection, NC_CHAR,
|
|
0, NULL, &NCDFVarID );
|
|
NCDF_ERR(status);
|
|
status = nc_put_att_text( cdfid, NCDFVarID, CF_GRD_MAPPING_NAME,
|
|
strlen(CF_PT_LATITUDE_LONGITUDE),
|
|
CF_PT_LATITUDE_LONGITUDE );
|
|
NCDF_ERR(status);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write CF-1.5 compliant common attributes */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* DATUM information */
|
|
dfTemp = oSRS.GetPrimeMeridian();
|
|
nc_put_att_double( cdfid, NCDFVarID, CF_PP_LONG_PRIME_MERIDIAN,
|
|
NC_DOUBLE, 1, &dfTemp );
|
|
dfTemp = oSRS.GetSemiMajor();
|
|
nc_put_att_double( cdfid, NCDFVarID, CF_PP_SEMI_MAJOR_AXIS,
|
|
NC_DOUBLE, 1, &dfTemp );
|
|
dfTemp = oSRS.GetInvFlattening();
|
|
nc_put_att_double( cdfid, NCDFVarID, CF_PP_INVERSE_FLATTENING,
|
|
NC_DOUBLE, 1, &dfTemp );
|
|
|
|
/* Optional GDAL custom projection tags */
|
|
if ( bWriteGDALTags == TRUE ) {
|
|
|
|
*szGeoTransform = '\0';
|
|
for( int i=0; i<6; i++ ) {
|
|
CPLsprintf( szTemp, "%.16g ",
|
|
adfGeoTransform[i] );
|
|
strcat( szGeoTransform, szTemp );
|
|
}
|
|
CPLDebug( "GDAL_netCDF", "szGeoTranform = %s", szGeoTransform );
|
|
|
|
// if ( strlen(pszProj4Defn) > 0 ) {
|
|
// nc_put_att_text( cdfid, NCDFVarID, "proj4",
|
|
// strlen( pszProj4Defn ), pszProj4Defn );
|
|
// }
|
|
nc_put_att_text( cdfid, NCDFVarID, NCDF_SPATIAL_REF,
|
|
strlen( pszProjection ), pszProjection );
|
|
/* for now write the geotransform for back-compat or else
|
|
the old (1.8.1) driver overrides the CF geotransform with
|
|
empty values from dfNN, dfSN, dfEE, dfWE; */
|
|
/* TODO: fix this in 1.8 branch, and then remove this here */
|
|
if ( bWriteGeoTransform && bSetGeoTransform ) {
|
|
nc_put_att_text( cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
|
|
strlen( szGeoTransform ),
|
|
szGeoTransform );
|
|
}
|
|
}
|
|
|
|
/* write projection variable to band variable */
|
|
/* need to call later if there are no bands */
|
|
AddGridMappingRef();
|
|
|
|
} /* end if( bWriteGridMapping ) */
|
|
|
|
pfnProgress( 0.10, NULL, pProgressData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write CF Projection vars */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write X/Y attributes */
|
|
/* -------------------------------------------------------------------- */
|
|
if( bIsProjected )
|
|
{
|
|
pszUnits = oSRS.GetAttrValue("PROJCS|UNIT",1);
|
|
if ( pszUnits == NULL || EQUAL(pszUnits,"1") )
|
|
strcpy(szUnits,"m");
|
|
else if ( EQUAL(pszUnits,"1000") )
|
|
strcpy(szUnits,"km");
|
|
|
|
/* X */
|
|
int anXDims[1];
|
|
anXDims[0] = nXDimID;
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d)",
|
|
cdfid, NCDF_DIMNAME_X, NC_DOUBLE );
|
|
status = nc_def_var( cdfid, NCDF_DIMNAME_X, NC_DOUBLE,
|
|
1, anXDims, &NCDFVarID );
|
|
NCDF_ERR(status);
|
|
nVarXID=NCDFVarID;
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_STD_NAME,
|
|
strlen(CF_PROJ_X_COORD),
|
|
CF_PROJ_X_COORD );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_LNG_NAME,
|
|
strlen(CF_PROJ_X_COORD_LONG_NAME),
|
|
CF_PROJ_X_COORD_LONG_NAME );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_UNITS, strlen(szUnits), szUnits );
|
|
|
|
/* Y */
|
|
int anYDims[1];
|
|
anYDims[0] = nYDimID;
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d)",
|
|
cdfid, NCDF_DIMNAME_Y, NC_DOUBLE );
|
|
status = nc_def_var( cdfid, NCDF_DIMNAME_Y, NC_DOUBLE,
|
|
1, anYDims, &NCDFVarID );
|
|
NCDF_ERR(status);
|
|
nVarYID=NCDFVarID;
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_STD_NAME,
|
|
strlen(CF_PROJ_Y_COORD),
|
|
CF_PROJ_Y_COORD );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_LNG_NAME,
|
|
strlen(CF_PROJ_Y_COORD_LONG_NAME),
|
|
CF_PROJ_Y_COORD_LONG_NAME );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_UNITS, strlen(szUnits), szUnits );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write lat/lon attributes if needed */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( bWriteLonLat ) {
|
|
int *panLatDims=NULL;
|
|
int *panLonDims=NULL;
|
|
int nLatDims=-1;
|
|
int nLonDims=-1;
|
|
|
|
/* get information */
|
|
if ( bHasGeoloc ) { /* geoloc */
|
|
nLatDims = 2;
|
|
panLatDims = (int *) CPLCalloc( nLatDims, sizeof( int ) );
|
|
panLatDims[0] = nYDimID;
|
|
panLatDims[1] = nXDimID;
|
|
nLonDims = 2;
|
|
panLonDims = (int *) CPLCalloc( nLonDims, sizeof( int ) );
|
|
panLonDims[0] = nYDimID;
|
|
panLonDims[1] = nXDimID;
|
|
}
|
|
else if ( bIsProjected ) { /* projected */
|
|
nLatDims = 2;
|
|
panLatDims = (int *) CPLCalloc( nLatDims, sizeof( int ) );
|
|
panLatDims[0] = nYDimID;
|
|
panLatDims[1] = nXDimID;
|
|
nLonDims = 2;
|
|
panLonDims = (int *) CPLCalloc( nLonDims, sizeof( int ) );
|
|
panLonDims[0] = nYDimID;
|
|
panLonDims[1] = nXDimID;
|
|
}
|
|
else { /* geographic */
|
|
nLatDims = 1;
|
|
panLatDims = (int *) CPLCalloc( nLatDims, sizeof( int ) );
|
|
panLatDims[0] = nYDimID;
|
|
nLonDims = 1;
|
|
panLonDims = (int *) CPLCalloc( nLonDims, sizeof( int ) );
|
|
panLonDims[0] = nXDimID;
|
|
}
|
|
|
|
/* def vars and attributes */
|
|
status = nc_def_var( cdfid, NCDF_DIMNAME_LAT, eLonLatType,
|
|
nLatDims, panLatDims, &NCDFVarID );
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
|
|
cdfid, NCDF_DIMNAME_LAT, eLonLatType, nLatDims, NCDFVarID );
|
|
NCDF_ERR(status);
|
|
DefVarDeflate( NCDFVarID, FALSE ); // don't set chunking
|
|
nVarLatID = NCDFVarID;
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_STD_NAME,
|
|
8,"latitude" );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_LNG_NAME,
|
|
8, "latitude" );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_UNITS,
|
|
13, "degrees_north" );
|
|
|
|
status = nc_def_var( cdfid, NCDF_DIMNAME_LON, eLonLatType,
|
|
nLonDims, panLonDims, &NCDFVarID );
|
|
CPLDebug( "GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
|
|
cdfid, NCDF_DIMNAME_LON, eLonLatType, nLatDims, NCDFVarID );
|
|
NCDF_ERR(status);
|
|
DefVarDeflate( NCDFVarID, FALSE ); // don't set chunking
|
|
nVarLonID = NCDFVarID;
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_STD_NAME,
|
|
9, "longitude" );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_LNG_NAME,
|
|
9, "longitude" );
|
|
nc_put_att_text( cdfid, NCDFVarID, CF_UNITS,
|
|
12, "degrees_east" );
|
|
/* free data */
|
|
CPLFree( panLatDims );
|
|
CPLFree( panLonDims );
|
|
|
|
}
|
|
|
|
pfnProgress( 0.50, NULL, pProgressData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get projection values */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
double dfX0, dfDX, dfY0, dfDY;
|
|
dfX0=0.0, dfDX=0.0, dfY0=0.0, dfDY=0.0;
|
|
double *padLonVal = NULL;
|
|
double *padLatVal = NULL; /* should use float for projected, save space */
|
|
|
|
if( bIsProjected )
|
|
{
|
|
// const char *pszProjection;
|
|
OGRSpatialReference oSRS;
|
|
OGRSpatialReference *poLatLonSRS = NULL;
|
|
OGRCoordinateTransformation *poTransform = NULL;
|
|
|
|
char *pszWKT = (char *) pszProjection;
|
|
oSRS.importFromWkt( &pszWKT );
|
|
|
|
double *padYVal = NULL;
|
|
double *padXVal = NULL;
|
|
size_t startX[1];
|
|
size_t countX[1];
|
|
size_t startY[1];
|
|
size_t countY[1];
|
|
|
|
CPLDebug("GDAL_netCDF", "Getting (X,Y) values" );
|
|
|
|
padXVal = (double *) CPLMalloc( nRasterXSize * sizeof( double ) );
|
|
padYVal = (double *) CPLMalloc( nRasterYSize * sizeof( double ) );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get Y values */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( ! bBottomUp )
|
|
dfY0 = adfGeoTransform[3];
|
|
else /* invert latitude values */
|
|
dfY0 = adfGeoTransform[3] + ( adfGeoTransform[5] * nRasterYSize );
|
|
dfDY = adfGeoTransform[5];
|
|
|
|
for( int j=0; j<nRasterYSize; j++ ) {
|
|
/* The data point is centered inside the pixel */
|
|
if ( ! bBottomUp )
|
|
padYVal[j] = dfY0 + (j+0.5)*dfDY ;
|
|
else /* invert latitude values */
|
|
padYVal[j] = dfY0 - (j+0.5)*dfDY ;
|
|
}
|
|
startX[0] = 0;
|
|
countX[0] = nRasterXSize;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get X values */
|
|
/* -------------------------------------------------------------------- */
|
|
dfX0 = adfGeoTransform[0];
|
|
dfDX = adfGeoTransform[1];
|
|
|
|
for( int i=0; i<nRasterXSize; i++ ) {
|
|
/* The data point is centered inside the pixel */
|
|
padXVal[i] = dfX0 + (i+0.5)*dfDX ;
|
|
}
|
|
startY[0] = 0;
|
|
countY[0] = nRasterYSize;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write X/Y values */
|
|
/* -------------------------------------------------------------------- */
|
|
/* make sure we are in data mode */
|
|
SetDefineMode( FALSE );
|
|
|
|
CPLDebug("GDAL_netCDF", "Writing X values" );
|
|
status = nc_put_vara_double( cdfid, nVarXID, startX,
|
|
countX, padXVal);
|
|
NCDF_ERR(status);
|
|
|
|
CPLDebug("GDAL_netCDF", "Writing Y values" );
|
|
status = nc_put_vara_double( cdfid, nVarYID, startY,
|
|
countY, padYVal);
|
|
NCDF_ERR(status);
|
|
|
|
pfnProgress( 0.20, NULL, pProgressData );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write lon/lat arrays (CF coordinates) if requested */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* Get OGR transform if GEOLOCATION is not available */
|
|
if ( bWriteLonLat && !bHasGeoloc ) {
|
|
poLatLonSRS = oSRS.CloneGeogCS();
|
|
if ( poLatLonSRS != NULL )
|
|
poTransform = OGRCreateCoordinateTransformation( &oSRS, poLatLonSRS );
|
|
/* if no OGR transform, then don't write CF lon/lat */
|
|
if( poTransform == NULL ) {
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to get Coordinate Transform" );
|
|
bWriteLonLat = FALSE;
|
|
}
|
|
}
|
|
|
|
if ( bWriteLonLat ) {
|
|
|
|
if ( ! bHasGeoloc )
|
|
CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)" );
|
|
else
|
|
CPLDebug("GDAL_netCDF", "writing (lon,lat) from GEOLOCATION arrays" );
|
|
|
|
int bOK = TRUE;
|
|
double dfProgress = 0.2;
|
|
int i,j;
|
|
|
|
size_t start[]={ 0, 0 };
|
|
size_t count[]={ 1, (size_t)nRasterXSize };
|
|
padLatVal = (double *) CPLMalloc( nRasterXSize * sizeof( double ) );
|
|
padLonVal = (double *) CPLMalloc( nRasterXSize * sizeof( double ) );
|
|
|
|
for( j = 0; (j < nRasterYSize) && bOK && (status == NC_NOERR); j++ ) {
|
|
|
|
start[0] = j;
|
|
|
|
/* get values from geotransform */
|
|
if ( ! bHasGeoloc ) {
|
|
/* fill values to transform */
|
|
for( i=0; i<nRasterXSize; i++ ) {
|
|
padLatVal[i] = padYVal[j];
|
|
padLonVal[i] = padXVal[i];
|
|
}
|
|
|
|
/* do the transform */
|
|
bOK = poTransform->Transform( nRasterXSize,
|
|
padLonVal, padLatVal, NULL );
|
|
if ( ! bOK ) {
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to Transform (X,Y) to (lon,lat).\n" );
|
|
}
|
|
}
|
|
/* get values from geoloc arrays */
|
|
else {
|
|
eErr = GDALRasterIO( hBand_Y, GF_Read,
|
|
0, j, nRasterXSize, 1,
|
|
padLatVal, nRasterXSize, 1,
|
|
GDT_Float64, 0, 0 );
|
|
if ( eErr == CE_None ) {
|
|
eErr = GDALRasterIO( hBand_X, GF_Read,
|
|
0, j, nRasterXSize, 1,
|
|
padLonVal, nRasterXSize, 1,
|
|
GDT_Float64, 0, 0 );
|
|
}
|
|
|
|
if ( eErr == CE_None )
|
|
bOK = TRUE;
|
|
else {
|
|
bOK = FALSE;
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to get scanline %d\n",j );
|
|
}
|
|
}
|
|
|
|
/* write data */
|
|
if ( bOK ) {
|
|
status = nc_put_vara_double( cdfid, nVarLatID, start,
|
|
count, padLatVal);
|
|
NCDF_ERR(status);
|
|
status = nc_put_vara_double( cdfid, nVarLonID, start,
|
|
count, padLonVal);
|
|
NCDF_ERR(status);
|
|
}
|
|
|
|
if ( j % (nRasterYSize/10) == 0 ) {
|
|
dfProgress += 0.08;
|
|
pfnProgress( dfProgress , NULL, pProgressData );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Free the srs and transform objects */
|
|
if ( poLatLonSRS != NULL ) CPLFree( poLatLonSRS );
|
|
if ( poTransform != NULL ) CPLFree( poTransform );
|
|
|
|
/* Free data */
|
|
CPLFree( padXVal );
|
|
CPLFree( padYVal );
|
|
CPLFree( padLonVal );
|
|
CPLFree( padLatVal);
|
|
|
|
} // projected
|
|
|
|
/* If not Projected assume Geographic to catch grids without Datum */
|
|
else if ( bWriteLonLat == TRUE ) {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get latitude values */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( ! bBottomUp )
|
|
dfY0 = adfGeoTransform[3];
|
|
else /* invert latitude values */
|
|
dfY0 = adfGeoTransform[3] + ( adfGeoTransform[5] * nRasterYSize );
|
|
dfDY = adfGeoTransform[5];
|
|
|
|
/* override lat values with the ones in GEOLOCATION/Y_VALUES */
|
|
if ( GetMetadataItem( "Y_VALUES", "GEOLOCATION" ) != NULL ) {
|
|
int nTemp = 0;
|
|
padLatVal = Get1DGeolocation( "Y_VALUES", nTemp );
|
|
/* make sure we got the correct amount, if not fallback to GT */
|
|
/* could add test fabs( fabs(padLatVal[0]) - fabs(dfY0) ) <= 0.1 ) ) */
|
|
if ( nTemp == nRasterYSize ) {
|
|
CPLDebug("GDAL_netCDF", "Using Y_VALUES geolocation metadata for lat values" );
|
|
}
|
|
else {
|
|
CPLDebug("GDAL_netCDF",
|
|
"Got %d elements from Y_VALUES geolocation metadata, need %d",
|
|
nTemp, nRasterYSize );
|
|
if ( padLatVal ) {
|
|
CPLFree( padLatVal );
|
|
padLatVal = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( padLatVal == NULL ) {
|
|
padLatVal = (double *) CPLMalloc( nRasterYSize * sizeof( double ) );
|
|
for( int i=0; i<nRasterYSize; i++ ) {
|
|
/* The data point is centered inside the pixel */
|
|
if ( ! bBottomUp )
|
|
padLatVal[i] = dfY0 + (i+0.5)*dfDY ;
|
|
else /* invert latitude values */
|
|
padLatVal[i] = dfY0 - (i+0.5)*dfDY ;
|
|
}
|
|
}
|
|
|
|
size_t startLat[1];
|
|
size_t countLat[1];
|
|
startLat[0] = 0;
|
|
countLat[0] = nRasterYSize;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get longitude values */
|
|
/* -------------------------------------------------------------------- */
|
|
dfX0 = adfGeoTransform[0];
|
|
dfDX = adfGeoTransform[1];
|
|
|
|
padLonVal = (double *) CPLMalloc( nRasterXSize * sizeof( double ) );
|
|
for( int i=0; i<nRasterXSize; i++ ) {
|
|
/* The data point is centered inside the pixel */
|
|
padLonVal[i] = dfX0 + (i+0.5)*dfDX ;
|
|
}
|
|
|
|
size_t startLon[1];
|
|
size_t countLon[1];
|
|
startLon[0] = 0;
|
|
countLon[0] = nRasterXSize;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write latitude and longitude values */
|
|
/* -------------------------------------------------------------------- */
|
|
/* make sure we are in data mode */
|
|
SetDefineMode( FALSE );
|
|
|
|
/* write values */
|
|
CPLDebug("GDAL_netCDF", "Writing lat values" );
|
|
|
|
status = nc_put_vara_double( cdfid, nVarLatID, startLat,
|
|
countLat, padLatVal);
|
|
NCDF_ERR(status);
|
|
|
|
CPLDebug("GDAL_netCDF", "Writing lon values" );
|
|
status = nc_put_vara_double( cdfid, nVarLonID, startLon,
|
|
countLon, padLonVal);
|
|
NCDF_ERR(status);
|
|
|
|
/* free values */
|
|
CPLFree( padLatVal );
|
|
CPLFree( padLonVal );
|
|
|
|
}// not projected
|
|
|
|
/* close geoloc datasets */
|
|
if ( bHasGeoloc ) {
|
|
GDALClose( hDS_X );
|
|
GDALClose( hDS_Y );
|
|
}
|
|
|
|
pfnProgress( 1.00, NULL, pProgressData );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/* Write Projection variable to band variable */
|
|
/* Moved from AddProjectionVars() for cases when bands are added after projection */
|
|
void netCDFDataset::AddGridMappingRef( )
|
|
{
|
|
int nVarId = -1;
|
|
int bOldDefineMode = bDefineMode;
|
|
|
|
if( (GetAccess() == GA_Update) &&
|
|
(nBands >= 1) && (GetRasterBand( 1 )) &&
|
|
pszCFProjection != NULL && ! EQUAL( pszCFProjection, "" ) ) {
|
|
|
|
nVarId = ( (netCDFRasterBand *) GetRasterBand( 1 ) )->nZId;
|
|
bAddedGridMappingRef = TRUE;
|
|
|
|
/* make sure we are in define mode */
|
|
SetDefineMode( TRUE );
|
|
status = nc_put_att_text( cdfid, nVarId,
|
|
CF_GRD_MAPPING,
|
|
strlen( pszCFProjection ),
|
|
pszCFProjection );
|
|
NCDF_ERR(status);
|
|
if ( pszCFCoordinates != NULL && ! EQUAL( pszCFCoordinates, "" ) ) {
|
|
status = nc_put_att_text( cdfid, nVarId,
|
|
CF_COORDINATES,
|
|
strlen( pszCFCoordinates ),
|
|
pszCFCoordinates );
|
|
NCDF_ERR(status);
|
|
}
|
|
|
|
/* go back to previous define mode */
|
|
SetDefineMode( bOldDefineMode );
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr netCDFDataset::GetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
|
|
if( bSetGeoTransform )
|
|
return CE_None;
|
|
else
|
|
return GDALPamDataset::GetGeoTransform( padfTransform );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* rint() */
|
|
/************************************************************************/
|
|
|
|
double netCDFDataset::rint( double dfX)
|
|
{
|
|
if( dfX > 0 ) {
|
|
int nX = (int) (dfX+0.5);
|
|
if( nX % 2 ) {
|
|
double dfDiff = dfX - (double)nX;
|
|
if( dfDiff == -0.5 )
|
|
return double( nX-1 );
|
|
}
|
|
return double( nX );
|
|
} else {
|
|
int nX= (int) (dfX-0.5);
|
|
if( nX % 2 ) {
|
|
double dfDiff = dfX - (double)nX;
|
|
if( dfDiff == 0.5 )
|
|
return double(nX+1);
|
|
}
|
|
return double(nX);
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ReadAttributes() */
|
|
/************************************************************************/
|
|
CPLErr netCDFDataset::ReadAttributes( int cdfid, int var)
|
|
|
|
{
|
|
char szAttrName[ NC_MAX_NAME ];
|
|
char szVarName [ NC_MAX_NAME ];
|
|
char szMetaName[ NC_MAX_NAME * 2 ];
|
|
char *pszMetaTemp = NULL;
|
|
int nbAttr;
|
|
|
|
nc_inq_varnatts( cdfid, var, &nbAttr );
|
|
if( var == NC_GLOBAL ) {
|
|
strcpy( szVarName,"NC_GLOBAL" );
|
|
}
|
|
else {
|
|
nc_inq_varname( cdfid, var, szVarName );
|
|
}
|
|
|
|
for( int l=0; l < nbAttr; l++) {
|
|
|
|
nc_inq_attname( cdfid, var, l, szAttrName);
|
|
sprintf( szMetaName, "%s#%s", szVarName, szAttrName );
|
|
|
|
if ( NCDFGetAttr( cdfid, var, szAttrName, &pszMetaTemp )
|
|
== CE_None ) {
|
|
papszMetadata = CSLSetNameValue(papszMetadata,
|
|
szMetaName,
|
|
pszMetaTemp);
|
|
CPLFree(pszMetaTemp);
|
|
pszMetaTemp = NULL;
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF", "invalid global metadata %s", szMetaName );
|
|
}
|
|
|
|
}
|
|
|
|
return CE_None;
|
|
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* netCDFDataset::CreateSubDatasetList() */
|
|
/************************************************************************/
|
|
void netCDFDataset::CreateSubDatasetList( )
|
|
{
|
|
|
|
char szDim[ MAX_NC_NAME ];
|
|
char szTemp[ MAX_NC_NAME ];
|
|
char szType[ MAX_NC_NAME ];
|
|
char szName[ MAX_NC_NAME ];
|
|
char szVarStdName[ MAX_NC_NAME ];
|
|
int nDims;
|
|
int nVar;
|
|
int nVarCount;
|
|
int i;
|
|
nc_type nVarType;
|
|
int *ponDimIds;
|
|
size_t nDimLen;
|
|
int nSub;
|
|
nc_type nAttype;
|
|
size_t nAttlen;
|
|
|
|
netCDFDataset *poDS;
|
|
poDS = this;
|
|
|
|
nSub=1;
|
|
nc_inq_nvars ( cdfid, &nVarCount );
|
|
|
|
for ( nVar = 0; nVar < nVarCount; nVar++ ) {
|
|
|
|
nc_inq_varndims ( cdfid, nVar, &nDims );
|
|
|
|
if( nDims >= 2 ) {
|
|
ponDimIds = (int *) CPLCalloc( nDims, sizeof( int ) );
|
|
nc_inq_vardimid ( cdfid, nVar, ponDimIds );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create Sub dataset list */
|
|
/* -------------------------------------------------------------------- */
|
|
szDim[0]='\0';
|
|
for( i = 0; i < nDims; i++ ) {
|
|
nc_inq_dimlen ( cdfid, ponDimIds[i], &nDimLen );
|
|
sprintf(szTemp, "%d", (int) nDimLen);
|
|
strcat(szTemp, "x" );
|
|
strcat(szDim, szTemp);
|
|
}
|
|
|
|
nc_inq_vartype( cdfid, nVar, &nVarType );
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get rid of the last "x" character */
|
|
/* -------------------------------------------------------------------- */
|
|
szDim[strlen(szDim) - 1] = '\0';
|
|
switch( nVarType ) {
|
|
|
|
case NC_BYTE:
|
|
strcpy(szType, "8-bit integer");
|
|
break;
|
|
case NC_CHAR:
|
|
strcpy(szType, "8-bit character");
|
|
break;
|
|
case NC_SHORT:
|
|
strcpy(szType, "16-bit integer");
|
|
break;
|
|
case NC_INT:
|
|
strcpy(szType, "32-bit integer");
|
|
break;
|
|
case NC_FLOAT:
|
|
strcpy(szType, "32-bit floating-point");
|
|
break;
|
|
case NC_DOUBLE:
|
|
strcpy(szType, "64-bit floating-point");
|
|
break;
|
|
#ifdef NETCDF_HAS_NC4
|
|
case NC_UBYTE:
|
|
strcpy(szType, "8-bit unsigned integer");
|
|
break;
|
|
case NC_USHORT:
|
|
strcpy(szType, "16-bit unsigned integer");
|
|
break;
|
|
case NC_UINT:
|
|
strcpy(szType, "32-bit unsigned integer");
|
|
break;
|
|
case NC_INT64:
|
|
strcpy(szType, "64-bit integer");
|
|
break;
|
|
case NC_UINT64:
|
|
strcpy(szType, "64-bit unsigned integer");
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
nc_inq_varname( cdfid, nVar, szName);
|
|
nc_inq_att( cdfid, nVar, CF_STD_NAME, &nAttype, &nAttlen);
|
|
if( nc_get_att_text ( cdfid, nVar, CF_STD_NAME,
|
|
szVarStdName ) == NC_NOERR ) {
|
|
szVarStdName[nAttlen] = '\0';
|
|
}
|
|
else {
|
|
strcpy( szVarStdName, szName );
|
|
}
|
|
|
|
sprintf( szTemp, "SUBDATASET_%d_NAME", nSub);
|
|
|
|
poDS->papszSubDatasets =
|
|
CSLSetNameValue( poDS->papszSubDatasets, szTemp,
|
|
CPLSPrintf( "NETCDF:\"%s\":%s",
|
|
poDS->osFilename.c_str(),
|
|
szName) ) ;
|
|
|
|
sprintf( szTemp, "SUBDATASET_%d_DESC", nSub++ );
|
|
|
|
poDS->papszSubDatasets =
|
|
CSLSetNameValue( poDS->papszSubDatasets, szTemp,
|
|
CPLSPrintf( "[%s] %s (%s)",
|
|
szDim,
|
|
szVarStdName,
|
|
szType ) );
|
|
|
|
CPLFree(ponDimIds);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IdentifyFormat() */
|
|
/************************************************************************/
|
|
|
|
int netCDFDataset::IdentifyFormat( GDALOpenInfo * poOpenInfo,
|
|
#ifndef HAVE_HDF5
|
|
CPL_UNUSED
|
|
#endif
|
|
bool bCheckExt = TRUE )
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Does this appear to be a netcdf file? If so, which format? */
|
|
/* http://www.unidata.ucar.edu/software/netcdf/docs/faq.html#fv1_5 */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if( EQUALN(poOpenInfo->pszFilename,"NETCDF:",7) )
|
|
return NCDF_FORMAT_UNKNOWN;
|
|
if ( poOpenInfo->nHeaderBytes < 4 )
|
|
return NCDF_FORMAT_NONE;
|
|
if ( EQUALN((char*)poOpenInfo->pabyHeader,"CDF\001",4) )
|
|
{
|
|
/* In case the netCDF driver is registered before the GMT driver, */
|
|
/* avoid opening GMT files */
|
|
if( GDALGetDriverByName("GMT") != NULL )
|
|
{
|
|
int bFoundZ = FALSE, bFoundDimension = FALSE;
|
|
for(int i=0;i<poOpenInfo->nHeaderBytes - 11;i++)
|
|
{
|
|
if( poOpenInfo->pabyHeader[i] == 1 &&
|
|
poOpenInfo->pabyHeader[i+1] == 'z' &&
|
|
poOpenInfo->pabyHeader[i+2] == 0 )
|
|
bFoundZ = TRUE;
|
|
else if( poOpenInfo->pabyHeader[i] == 9 &&
|
|
memcmp((const char*)poOpenInfo->pabyHeader + i + 1, "dimension", 9) == 0 &&
|
|
poOpenInfo->pabyHeader[i+10] == 0 )
|
|
bFoundDimension = TRUE;
|
|
}
|
|
if( bFoundZ && bFoundDimension )
|
|
return NCDF_FORMAT_UNKNOWN;
|
|
}
|
|
|
|
return NCDF_FORMAT_NC;
|
|
}
|
|
else if ( EQUALN((char*)poOpenInfo->pabyHeader,"CDF\002",4) )
|
|
return NCDF_FORMAT_NC2;
|
|
else if ( EQUALN((char*)poOpenInfo->pabyHeader,"\211HDF\r\n\032\n",8) ) {
|
|
/* Requires netCDF-4/HDF5 support in libnetcdf (not just libnetcdf-v4).
|
|
If HDF5 is not supported in GDAL, this driver will try to open the file
|
|
Else, make sure this driver does not try to open HDF5 files
|
|
If user really wants to open with this driver, use NETCDF:file.h5 format.
|
|
This check should be relaxed, but there is no clear way to make a difference.
|
|
*/
|
|
|
|
/* Check for HDF5 support in GDAL */
|
|
#ifdef HAVE_HDF5
|
|
if ( bCheckExt ) { /* Check by default */
|
|
const char* pszExtension = CPLGetExtension( poOpenInfo->pszFilename );
|
|
if ( ! ( EQUAL( pszExtension, "nc") || EQUAL( pszExtension, "cdf")
|
|
|| EQUAL( pszExtension, "nc2") || EQUAL( pszExtension, "nc4")
|
|
|| EQUAL( pszExtension, "nc3") || EQUAL( pszExtension, "grd") ) )
|
|
return NCDF_FORMAT_HDF5;
|
|
}
|
|
#endif
|
|
|
|
/* Check for netcdf-4 support in libnetcdf */
|
|
#ifdef NETCDF_HAS_NC4
|
|
return NCDF_FORMAT_NC4;
|
|
#else
|
|
return NCDF_FORMAT_HDF5;
|
|
#endif
|
|
|
|
}
|
|
else if ( EQUALN((char*)poOpenInfo->pabyHeader,"\016\003\023\001",4) ) {
|
|
/* Requires HDF4 support in libnetcdf, but if HF4 is supported by GDAL don't try to open. */
|
|
/* If user really wants to open with this driver, use NETCDF:file.hdf syntax. */
|
|
|
|
/* Check for HDF4 support in GDAL */
|
|
#ifdef HAVE_HDF4
|
|
if ( bCheckExt ) { /* Check by default */
|
|
/* Always treat as HDF4 file */
|
|
return NCDF_FORMAT_HDF4;
|
|
}
|
|
#endif
|
|
|
|
/* Check for HDF4 support in libnetcdf */
|
|
#ifdef NETCDF_HAS_HDF4
|
|
return NCDF_FORMAT_NC4;
|
|
#else
|
|
return NCDF_FORMAT_HDF4;
|
|
#endif
|
|
}
|
|
|
|
return NCDF_FORMAT_NONE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int netCDFDataset::Identify( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if( EQUALN(poOpenInfo->pszFilename,"NETCDF:",7) ) {
|
|
return TRUE;
|
|
}
|
|
int nTmpFormat = IdentifyFormat( poOpenInfo );
|
|
if( NCDF_FORMAT_NC == nTmpFormat ||
|
|
NCDF_FORMAT_NC2 == nTmpFormat ||
|
|
NCDF_FORMAT_NC4 == nTmpFormat ||
|
|
NCDF_FORMAT_NC4C == nTmpFormat )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *netCDFDataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
int j;
|
|
unsigned int k;
|
|
int nd;
|
|
int cdfid, dim_count, var, var_count;
|
|
int i = 0;
|
|
size_t lev_count;
|
|
size_t nTotLevCount = 1;
|
|
int nDim = 2;
|
|
int status;
|
|
int nDimID;
|
|
char szConventions[NC_MAX_NAME];
|
|
int ndims, nvars, ngatts, unlimdimid;
|
|
int nCount=0;
|
|
int nVarID=-1;
|
|
|
|
int nTmpFormat=NCDF_FORMAT_NONE;
|
|
int *panBandDimPos=NULL; // X, Y, Z postion in array
|
|
int *panBandZLev=NULL;
|
|
int *paDimIds=NULL;
|
|
size_t xdim, ydim;
|
|
char szTemp[NC_MAX_NAME];
|
|
|
|
CPLString osSubdatasetName;
|
|
int bTreatAsSubdataset;
|
|
|
|
char **papszIgnoreVars = NULL;
|
|
char *pszTemp = NULL;
|
|
int nIgnoredVars = 0;
|
|
|
|
char szDimName[NC_MAX_NAME];
|
|
char szExtraDimNames[NC_MAX_NAME];
|
|
char szExtraDimDef[NC_MAX_NAME];
|
|
nc_type nType=NC_NAT;
|
|
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "\n=====\nOpen(), filename=[%s]", poOpenInfo->pszFilename );
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Does this appear to be a netcdf file? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( ! EQUALN(poOpenInfo->pszFilename,"NETCDF:",7) ) {
|
|
nTmpFormat = IdentifyFormat( poOpenInfo );
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "identified format %d", nTmpFormat );
|
|
#endif
|
|
/* Note: not calling Identify() directly, because we want the file type */
|
|
/* Only support NCDF_FORMAT* formats */
|
|
if( ! ( NCDF_FORMAT_NC == nTmpFormat ||
|
|
NCDF_FORMAT_NC2 == nTmpFormat ||
|
|
NCDF_FORMAT_NC4 == nTmpFormat ||
|
|
NCDF_FORMAT_NC4C == nTmpFormat ) )
|
|
return NULL;
|
|
}
|
|
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
netCDFDataset *poDS;
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
poDS = new netCDFDataset();
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check if filename start with NETCDF: tag */
|
|
/* -------------------------------------------------------------------- */
|
|
if( EQUALN( poOpenInfo->pszFilename,"NETCDF:",7) )
|
|
{
|
|
char **papszName =
|
|
CSLTokenizeString2( poOpenInfo->pszFilename,
|
|
":", CSLT_HONOURSTRINGS|CSLT_PRESERVEESCAPES );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for drive name in windows NETCDF:"D:\... */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( CSLCount(papszName) == 4 &&
|
|
strlen(papszName[1]) == 1 &&
|
|
(papszName[2][0] == '/' || papszName[2][0] == '\\') )
|
|
{
|
|
poDS->osFilename = papszName[1];
|
|
poDS->osFilename += ':';
|
|
poDS->osFilename += papszName[2];
|
|
osSubdatasetName = papszName[3];
|
|
bTreatAsSubdataset = TRUE;
|
|
CSLDestroy( papszName );
|
|
}
|
|
else if( CSLCount(papszName) == 3 )
|
|
{
|
|
poDS->osFilename = papszName[1];
|
|
osSubdatasetName = papszName[2];
|
|
bTreatAsSubdataset = TRUE;
|
|
CSLDestroy( papszName );
|
|
}
|
|
else if( CSLCount(papszName) == 2 )
|
|
{
|
|
poDS->osFilename = papszName[1];
|
|
osSubdatasetName = "";
|
|
bTreatAsSubdataset = FALSE;
|
|
CSLDestroy( papszName );
|
|
}
|
|
else
|
|
{
|
|
CSLDestroy( papszName );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Failed to parse NETCDF: prefix string into expected 2, 3 or 4 fields." );
|
|
return NULL;
|
|
}
|
|
/* Identify Format from real file, with bCheckExt=FALSE */
|
|
GDALOpenInfo* poOpenInfo2 = new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly );
|
|
poDS->nFormat = IdentifyFormat( poOpenInfo2, FALSE );
|
|
delete poOpenInfo2;
|
|
if( NCDF_FORMAT_NONE == poDS->nFormat ||
|
|
NCDF_FORMAT_UNKNOWN == poDS->nFormat ) {
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
poDS->osFilename = poOpenInfo->pszFilename;
|
|
bTreatAsSubdataset = FALSE;
|
|
poDS->nFormat = nTmpFormat;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Try opening the dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "calling nc_open( %s )", poDS->osFilename.c_str() );
|
|
#endif
|
|
if( nc_open( poDS->osFilename, NC_NOWRITE, &cdfid ) != NC_NOERR ) {
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "error opening" );
|
|
#endif
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "got cdfid=%d\n", cdfid );
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Is this a real netCDF file? */
|
|
/* -------------------------------------------------------------------- */
|
|
status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
|
|
if( status != NC_NOERR ) {
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get file type from netcdf */
|
|
/* -------------------------------------------------------------------- */
|
|
status = nc_inq_format (cdfid, &nTmpFormat);
|
|
if ( status != NC_NOERR ) {
|
|
NCDF_ERR(status);
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"driver detected file type=%d, libnetcdf detected type=%d",
|
|
poDS->nFormat, nTmpFormat );
|
|
if ( nTmpFormat != poDS->nFormat ) {
|
|
/* warn if file detection conflicts with that from libnetcdf */
|
|
/* except for NC4C, which we have no way of detecting initially */
|
|
if ( nTmpFormat != NCDF_FORMAT_NC4C ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"NetCDF driver detected file type=%d, but libnetcdf detected type=%d",
|
|
poDS->nFormat, nTmpFormat );
|
|
}
|
|
CPLDebug( "GDAL_netCDF", "seting file type to %d, was %d",
|
|
nTmpFormat, poDS->nFormat );
|
|
poDS->nFormat = nTmpFormat;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Confirm the requested access is supported. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poOpenInfo->eAccess == GA_Update )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The NETCDF driver does not support update access to existing"
|
|
" datasets.\n" );
|
|
nc_close( cdfid );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Does the request variable exist? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( bTreatAsSubdataset )
|
|
{
|
|
status = nc_inq_varid( cdfid, osSubdatasetName, &var);
|
|
if( status != NC_NOERR ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"%s is a netCDF file, but %s is not a variable.",
|
|
poOpenInfo->pszFilename,
|
|
osSubdatasetName.c_str() );
|
|
|
|
nc_close( cdfid );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if( nc_inq_ndims( cdfid, &dim_count ) != NC_NOERR || dim_count < 2 )
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"%s is a netCDF file, but not in GMT configuration.",
|
|
poOpenInfo->pszFilename );
|
|
|
|
nc_close( cdfid );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
CPLDebug( "GDAL_netCDF", "dim_count = %d", dim_count );
|
|
|
|
szConventions[0] = '\0';
|
|
if( (status = nc_get_att_text( cdfid, NC_GLOBAL, "Conventions",
|
|
szConventions )) != NC_NOERR ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"No UNIDATA NC_GLOBAL:Conventions attribute");
|
|
/* note that 'Conventions' is always capital 'C' in CF spec*/
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( nc_inq_nvars ( cdfid, &var_count) != NC_NOERR )
|
|
{
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
CPLDebug( "GDAL_netCDF", "var_count = %d", var_count );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* Create Netcdf Subdataset if filename as NETCDF tag */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->cdfid = cdfid;
|
|
|
|
poDS->ReadAttributes( cdfid, NC_GLOBAL );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Identify variables that we should ignore as Raster Bands. */
|
|
/* Variables that are identified in other variable's "coordinate" and */
|
|
/* "bounds" attribute should not be treated as Raster Bands. */
|
|
/* See CF sections 5.2, 5.6 and 7.1 */
|
|
/* -------------------------------------------------------------------- */
|
|
for ( j = 0; j < nvars; j++ ) {
|
|
char **papszTokens = NULL;
|
|
if ( NCDFGetAttr( cdfid, j, "coordinates", &pszTemp ) == CE_None ) {
|
|
papszTokens = CSLTokenizeString2( pszTemp, " ", 0 );
|
|
for ( i=0; i<CSLCount(papszTokens); i++ ) {
|
|
papszIgnoreVars = CSLAddString( papszIgnoreVars, papszTokens[i] );
|
|
}
|
|
if ( papszTokens) CSLDestroy( papszTokens );
|
|
CPLFree( pszTemp );
|
|
}
|
|
if ( NCDFGetAttr( cdfid, j, "bounds", &pszTemp ) == CE_None &&
|
|
pszTemp != NULL ) {
|
|
if ( !EQUAL( pszTemp, "" ) )
|
|
papszIgnoreVars = CSLAddString( papszIgnoreVars, pszTemp );
|
|
CPLFree( pszTemp );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Filter variables (valid 2D raster bands) */
|
|
/* -------------------------------------------------------------------- */
|
|
for ( j = 0; j < nvars; j++ ) {
|
|
nc_inq_varndims ( cdfid, j, &ndims );
|
|
/* should we ignore this variable ? */
|
|
status = nc_inq_varname( cdfid, j, szTemp );
|
|
if ( status == NC_NOERR &&
|
|
( CSLFindString( papszIgnoreVars, szTemp ) != -1 ) ) {
|
|
nIgnoredVars++;
|
|
CPLDebug( "GDAL_netCDF", "variable #%d [%s] was ignored",j, szTemp);
|
|
}
|
|
/* only accept 2+D vars */
|
|
else if( ndims >= 2 ) {
|
|
nVarID=j;
|
|
nCount++;
|
|
}
|
|
}
|
|
|
|
if ( papszIgnoreVars )
|
|
CSLDestroy( papszIgnoreVars );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We have more than one variable with 2 dimensions in the */
|
|
/* file, then treat this as a subdataset container dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( (nCount > 1) && !bTreatAsSubdataset )
|
|
{
|
|
poDS->CreateSubDatasetList();
|
|
poDS->SetMetadata( poDS->papszMetadata );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
poDS->TryLoadXML();
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return( poDS );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* If we are not treating things as a subdataset, then capture */
|
|
/* the name of the single available variable as the subdataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( !bTreatAsSubdataset ) // nCount must be 1!
|
|
{
|
|
char szVarName[NC_MAX_NAME];
|
|
szVarName[0] = '\0';
|
|
nc_inq_varname( cdfid, nVarID, szVarName);
|
|
osSubdatasetName = szVarName;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We have ignored at least one variable, so we should report them */
|
|
/* as subdatasets for reference. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( (nIgnoredVars > 0) && !bTreatAsSubdataset )
|
|
{
|
|
CPLDebug( "GDAL_netCDF",
|
|
"As %d variables were ignored, creating subdataset list "
|
|
"for reference. Variable #%d [%s] is the main variable",
|
|
nIgnoredVars, nVarID, osSubdatasetName.c_str() );
|
|
poDS->CreateSubDatasetList();
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Open the NETCDF subdataset NETCDF:"filename":subdataset */
|
|
/* -------------------------------------------------------------------- */
|
|
var=-1;
|
|
nc_inq_varid( cdfid, osSubdatasetName, &var);
|
|
nd = 0;
|
|
nc_inq_varndims ( cdfid, var, &nd );
|
|
|
|
paDimIds = (int *)CPLCalloc(nd, sizeof( int ) );
|
|
panBandDimPos = ( int * ) CPLCalloc( nd, sizeof( int ) );
|
|
|
|
nc_inq_vardimid( cdfid, var, paDimIds );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check if somebody tried to pass a variable with less than 2D */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( nd < 2 ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Variable has %d dimension(s) - not supported.", nd );
|
|
CPLFree( paDimIds );
|
|
CPLFree( panBandDimPos );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* CF-1 Convention */
|
|
/* dimensions to appear in the relative order T, then Z, then Y, */
|
|
/* then X to the file. All other dimensions should, whenever */
|
|
/* possible, be placed to the left of the spatiotemporal */
|
|
/* dimensions. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order */
|
|
/* Ideally we should detect for other ordering and act accordingly */
|
|
/* Only done if file has Conventions=CF-* and only prints warning */
|
|
/* To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only */
|
|
/* attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int bCheckDims = FALSE;
|
|
bCheckDims =
|
|
( CSLTestBoolean( CPLGetConfigOption( "GDAL_NETCDF_VERIFY_DIMS", "YES" ) )
|
|
!= FALSE ) && EQUALN( szConventions, "CF", 2 );
|
|
|
|
if ( bCheckDims ) {
|
|
char szDimName1[NC_MAX_NAME], szDimName2[NC_MAX_NAME],
|
|
szDimName3[NC_MAX_NAME], szDimName4[NC_MAX_NAME];
|
|
szDimName1[0]='\0';
|
|
szDimName2[0]='\0';
|
|
szDimName3[0]='\0';
|
|
szDimName4[0]='\0';
|
|
nc_inq_dimname( cdfid, paDimIds[nd-1], szDimName1 );
|
|
nc_inq_dimname( cdfid, paDimIds[nd-2], szDimName2 );
|
|
if ( NCDFIsVarLongitude( cdfid, -1, szDimName1 )==FALSE &&
|
|
NCDFIsVarProjectionX( cdfid, -1, szDimName1 )==FALSE ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"dimension #%d (%s) is not a Longitude/X dimension.",
|
|
nd-1, szDimName1 );
|
|
}
|
|
if ( NCDFIsVarLatitude( cdfid, -1, szDimName2 )==FALSE &&
|
|
NCDFIsVarProjectionY( cdfid, -1, szDimName2 )==FALSE ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"dimension #%d (%s) is not a Latitude/Y dimension.",
|
|
nd-2, szDimName2 );
|
|
}
|
|
if ( nd >= 3 ) {
|
|
nc_inq_dimname( cdfid, paDimIds[nd-3], szDimName3 );
|
|
if ( nd >= 4 ) {
|
|
nc_inq_dimname( cdfid, paDimIds[nd-4], szDimName4 );
|
|
if ( NCDFIsVarVerticalCoord( cdfid, -1, szDimName3 )==FALSE ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"dimension #%d (%s) is not a Time dimension.",
|
|
nd-3, szDimName3 );
|
|
}
|
|
if ( NCDFIsVarTimeCoord( cdfid, -1, szDimName4 )==FALSE ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"dimension #%d (%s) is not a Time dimension.",
|
|
nd-4, szDimName4 );
|
|
}
|
|
}
|
|
else {
|
|
if ( NCDFIsVarVerticalCoord( cdfid, -1, szDimName3 )==FALSE &&
|
|
NCDFIsVarTimeCoord( cdfid, -1, szDimName3 )==FALSE ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"dimension #%d (%s) is not a Time or Vertical dimension.",
|
|
nd-3, szDimName3 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get X dimensions information */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->nXDimID = paDimIds[nd-1];
|
|
nc_inq_dimlen ( cdfid, poDS->nXDimID, &xdim );
|
|
poDS->nRasterXSize = xdim;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get Y dimension information */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->nYDimID = paDimIds[nd-2];
|
|
nc_inq_dimlen ( cdfid, poDS->nYDimID, &ydim );
|
|
poDS->nRasterYSize = ydim;
|
|
|
|
|
|
for( j=0,k=0; j < nd; j++ ){
|
|
if( paDimIds[j] == poDS->nXDimID ){
|
|
panBandDimPos[0] = j; // Save Position of XDim
|
|
k++;
|
|
}
|
|
if( paDimIds[j] == poDS->nYDimID ){
|
|
panBandDimPos[1] = j; // Save Position of YDim
|
|
k++;
|
|
}
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* X and Y Dimension Ids were not found! */
|
|
/* -------------------------------------------------------------------- */
|
|
if( k != 2 ) {
|
|
CPLFree( paDimIds );
|
|
CPLFree( panBandDimPos );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read Metadata for this variable */
|
|
/* -------------------------------------------------------------------- */
|
|
/* should disable as is also done at band level, except driver needs the
|
|
variables as metadata (e.g. projection) */
|
|
poDS->ReadAttributes( cdfid, var );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read Metadata for each dimension */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
for( j=0; j < dim_count; j++ ){
|
|
nc_inq_dimname( cdfid, j, szTemp );
|
|
poDS->papszDimName.AddString( szTemp );
|
|
status = nc_inq_varid( cdfid, poDS->papszDimName[j], &nDimID );
|
|
if( status == NC_NOERR ) {
|
|
poDS->ReadAttributes( cdfid, nDimID );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set projection info */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetProjectionFromVar( var );
|
|
|
|
/* override bottom-up with GDAL_NETCDF_BOTTOMUP config option */
|
|
const char *pszValue = CPLGetConfigOption( "GDAL_NETCDF_BOTTOMUP", NULL );
|
|
if ( pszValue ) {
|
|
poDS->bBottomUp = CSLTestBoolean( pszValue ) != FALSE;
|
|
CPLDebug( "GDAL_netCDF",
|
|
"set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
|
|
poDS->bBottomUp, pszValue );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Save non-spatial dimension info */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
nTotLevCount = 1;
|
|
if ( nd > 2 ) {
|
|
nDim=2;
|
|
panBandZLev = (int *)CPLCalloc( nd-2, sizeof( int ) );
|
|
|
|
strcpy( szExtraDimNames, (char*)"{");
|
|
|
|
for( j=0; j < nd; j++ ){
|
|
if( ( paDimIds[j] != poDS->nXDimID ) &&
|
|
( paDimIds[j] != poDS->nYDimID ) ){
|
|
nc_inq_dimlen ( cdfid, paDimIds[j], &lev_count );
|
|
nTotLevCount *= lev_count;
|
|
panBandZLev[ nDim-2 ] = lev_count;
|
|
panBandDimPos[ nDim++ ] = j; //Save Position of ZDim
|
|
//Save non-spatial dimension names
|
|
if ( nc_inq_dimname( cdfid, paDimIds[j], szDimName )
|
|
== NC_NOERR ) {
|
|
strcat( szExtraDimNames, szDimName );
|
|
if ( j < nd-3 ) {
|
|
strcat( szExtraDimNames, (char *)"," );
|
|
}
|
|
nc_inq_varid( cdfid, szDimName, &nVarID );
|
|
nc_inq_vartype( cdfid, nVarID, &nType );
|
|
sprintf( szExtraDimDef, "{%ld,%d}", (long)lev_count, nType );
|
|
sprintf( szTemp, "NETCDF_DIM_%s_DEF", szDimName );
|
|
poDS->papszMetadata = CSLSetNameValue( poDS->papszMetadata,
|
|
szTemp, szExtraDimDef );
|
|
if ( NCDFGet1DVar( cdfid, nVarID, &pszTemp ) == CE_None ) {
|
|
sprintf( szTemp, "NETCDF_DIM_%s_VALUES", szDimName );
|
|
poDS->papszMetadata = CSLSetNameValue( poDS->papszMetadata,
|
|
szTemp, pszTemp );
|
|
CPLFree( pszTemp );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
strcat( szExtraDimNames, (char *)"}" );
|
|
poDS->papszMetadata = CSLSetNameValue( poDS->papszMetadata,
|
|
"NETCDF_DIM_EXTRA", szExtraDimNames );
|
|
}
|
|
i=0;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Store Metadata */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetMetadata( poDS->papszMetadata );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create bands */
|
|
/* -------------------------------------------------------------------- */
|
|
for ( unsigned int lev = 0; lev < nTotLevCount ; lev++ ) {
|
|
netCDFRasterBand *poBand =
|
|
new netCDFRasterBand(poDS, var, nDim, lev,
|
|
panBandZLev, panBandDimPos,
|
|
paDimIds, i+1 );
|
|
poDS->SetBand( i+1, poBand );
|
|
i++;
|
|
}
|
|
|
|
CPLFree( paDimIds );
|
|
CPLFree( panBandDimPos );
|
|
if ( panBandZLev )
|
|
CPLFree( panBandZLev );
|
|
|
|
poDS->nBands = i;
|
|
|
|
// Handle angular geographic coordinates here
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( bTreatAsSubdataset )
|
|
{
|
|
poDS->SetPhysicalFilename( poDS->osFilename );
|
|
poDS->SetSubdatasetName( osSubdatasetName );
|
|
}
|
|
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
poDS->TryLoadXML();
|
|
|
|
if( bTreatAsSubdataset )
|
|
poDS->oOvManager.Initialize( poDS, ":::VIRTUAL:::" );
|
|
else
|
|
poDS->oOvManager.Initialize( poDS, poDS->osFilename );
|
|
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* CopyMetadata() */
|
|
/* */
|
|
/* Create a copy of metadata for NC_GLOBAL or a variable */
|
|
/************************************************************************/
|
|
|
|
void CopyMetadata( void *poDS, int fpImage, int CDFVarID,
|
|
const char *pszPrefix, int bIsBand ) {
|
|
|
|
char **papszMetadata=NULL;
|
|
char **papszFieldData=NULL;
|
|
const char *pszField;
|
|
char szMetaName[ NCDF_MAX_STR_LEN ];
|
|
size_t nAttrValueSize = NCDF_MAX_STR_LEN;
|
|
char *pszMetaValue = (char *) CPLMalloc(sizeof(char) * nAttrValueSize);
|
|
*pszMetaValue = '\0';
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
int nItems;
|
|
|
|
/* Remove the following band meta but set them later from band data */
|
|
const char *papszIgnoreBand[] = { CF_ADD_OFFSET, CF_SCALE_FACTOR,
|
|
"valid_range", "_Unsigned",
|
|
_FillValue, "coordinates",
|
|
NULL };
|
|
const char *papszIgnoreGlobal[] = { "NETCDF_DIM_EXTRA", NULL };
|
|
|
|
if( CDFVarID == NC_GLOBAL ) {
|
|
papszMetadata = GDALGetMetadata( (GDALDataset *) poDS,"");
|
|
} else {
|
|
papszMetadata = GDALGetMetadata( (GDALRasterBandH) poDS, NULL );
|
|
}
|
|
|
|
nItems = CSLCount( papszMetadata );
|
|
|
|
for(int k=0; k < nItems; k++ ) {
|
|
pszField = CSLGetField( papszMetadata, k );
|
|
if ( papszFieldData ) CSLDestroy( papszFieldData );
|
|
papszFieldData = CSLTokenizeString2 (pszField, "=",
|
|
CSLT_HONOURSTRINGS );
|
|
if( papszFieldData[1] != NULL ) {
|
|
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "copy metadata [%s]=[%s]",
|
|
papszFieldData[ 0 ], papszFieldData[ 1 ] );
|
|
#endif
|
|
|
|
strcpy( szMetaName, papszFieldData[ 0 ] );
|
|
NCDFSafeStrcpy(&pszMetaValue, papszFieldData[ 1 ], &nAttrValueSize);
|
|
|
|
/* check for items that match pszPrefix if applicable */
|
|
if ( ( pszPrefix != NULL ) && ( !EQUAL( pszPrefix, "" ) ) ) {
|
|
/* remove prefix */
|
|
if ( EQUALN( szMetaName, pszPrefix, strlen(pszPrefix) ) ) {
|
|
strcpy( szTemp, szMetaName+strlen(pszPrefix) );
|
|
strcpy( szMetaName, szTemp );
|
|
}
|
|
/* only copy items that match prefix */
|
|
else
|
|
continue;
|
|
}
|
|
|
|
/* Fix various issues with metadata translation */
|
|
if( CDFVarID == NC_GLOBAL ) {
|
|
/* Do not copy items in papszIgnoreGlobal and NETCDF_DIM_* */
|
|
if ( ( CSLFindString( (char **)papszIgnoreGlobal, szMetaName ) != -1 ) ||
|
|
( strncmp( szMetaName, "NETCDF_DIM_", 11 ) == 0 ) )
|
|
continue;
|
|
/* Remove NC_GLOBAL prefix for netcdf global Metadata */
|
|
else if( strncmp( szMetaName, "NC_GLOBAL#", 10 ) == 0 ) {
|
|
strcpy( szTemp, szMetaName+10 );
|
|
strcpy( szMetaName, szTemp );
|
|
}
|
|
/* GDAL Metadata renamed as GDAL-[meta] */
|
|
else if ( strstr( szMetaName, "#" ) == NULL ) {
|
|
strcpy( szTemp, "GDAL_" );
|
|
strcat( szTemp, szMetaName );
|
|
strcpy( szMetaName, szTemp );
|
|
}
|
|
/* Keep time, lev and depth information for safe-keeping */
|
|
/* Time and vertical coordinate handling need improvements */
|
|
/*
|
|
else if( strncmp( szMetaName, "time#", 5 ) == 0 ) {
|
|
szMetaName[4] = '-';
|
|
}
|
|
else if( strncmp( szMetaName, "lev#", 4 ) == 0 ) {
|
|
szMetaName[3] = '-';
|
|
}
|
|
else if( strncmp( szMetaName, "depth#", 6 ) == 0 ) {
|
|
szMetaName[5] = '-';
|
|
}
|
|
*/
|
|
/* Only copy data without # (previously all data was copied) */
|
|
if ( strstr( szMetaName, "#" ) != NULL )
|
|
continue;
|
|
// /* netCDF attributes do not like the '#' character. */
|
|
// for( unsigned int h=0; h < strlen( szMetaName ) -1 ; h++ ) {
|
|
// if( szMetaName[h] == '#' ) szMetaName[h] = '-';
|
|
// }
|
|
}
|
|
else {
|
|
/* Do not copy varname, stats, NETCDF_DIM_*, nodata
|
|
and items in papszIgnoreBand */
|
|
if ( ( strncmp( szMetaName, "NETCDF_VARNAME", 14) == 0 ) ||
|
|
( strncmp( szMetaName, "STATISTICS_", 11) == 0 ) ||
|
|
( strncmp( szMetaName, "NETCDF_DIM_", 11 ) == 0 ) ||
|
|
( strncmp( szMetaName, "missing_value", 13 ) == 0 ) ||
|
|
( strncmp( szMetaName, "_FillValue", 10 ) == 0 ) ||
|
|
( CSLFindString( (char **)papszIgnoreBand, szMetaName ) != -1 ) )
|
|
continue;
|
|
}
|
|
|
|
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "copy name=[%s] value=[%s]",
|
|
szMetaName, pszMetaValue );
|
|
#endif
|
|
if ( NCDFPutAttr( fpImage, CDFVarID,szMetaName,
|
|
pszMetaValue ) != CE_None )
|
|
CPLDebug( "GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
|
|
fpImage, CDFVarID,szMetaName, pszMetaValue );
|
|
}
|
|
}
|
|
|
|
if ( papszFieldData ) CSLDestroy( papszFieldData );
|
|
CPLFree( pszMetaValue );
|
|
|
|
/* Set add_offset and scale_factor here if present */
|
|
if( ( CDFVarID != NC_GLOBAL ) && ( bIsBand ) ) {
|
|
|
|
int bGotAddOffset, bGotScale;
|
|
GDALRasterBandH poRB = (GDALRasterBandH) poDS;
|
|
double dfAddOffset = GDALGetRasterOffset( poRB , &bGotAddOffset );
|
|
double dfScale = GDALGetRasterScale( poRB, &bGotScale );
|
|
|
|
if ( bGotAddOffset && dfAddOffset != 0.0 && bGotScale && dfScale != 1.0 ) {
|
|
GDALSetRasterOffset( poRB, dfAddOffset );
|
|
GDALSetRasterScale( poRB, dfScale );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* CreateLL() */
|
|
/* */
|
|
/* Shared functionality between netCDFDataset::Create() and */
|
|
/* netCDF::CreateCopy() for creating netcdf file based on a set of */
|
|
/* options and a configuration. */
|
|
/************************************************************************/
|
|
|
|
netCDFDataset *
|
|
netCDFDataset::CreateLL( const char * pszFilename,
|
|
int nXSize, int nYSize, CPL_UNUSED int nBands,
|
|
char ** papszOptions )
|
|
{
|
|
int status = NC_NOERR;
|
|
netCDFDataset *poDS;
|
|
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
poDS = new netCDFDataset();
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
|
|
poDS->nRasterXSize = nXSize;
|
|
poDS->nRasterYSize = nYSize;
|
|
poDS->eAccess = GA_Update;
|
|
poDS->osFilename = pszFilename;
|
|
|
|
/* from gtiff driver, is this ok? */
|
|
/*
|
|
poDS->nBlockXSize = nXSize;
|
|
poDS->nBlockYSize = 1;
|
|
poDS->nBlocksPerBand =
|
|
((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
|
|
* ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
|
|
*/
|
|
|
|
/* process options */
|
|
poDS->papszCreationOptions = CSLDuplicate( papszOptions );
|
|
poDS->ProcessCreationOptions( );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create the dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
status = nc_create( pszFilename, poDS->nCreateMode, &(poDS->cdfid) );
|
|
|
|
/* put into define mode */
|
|
poDS->SetDefineMode(TRUE);
|
|
|
|
if( status != NC_NOERR )
|
|
{
|
|
CPLError( CE_Failure, CPLE_OpenFailed,
|
|
"Unable to create netCDF file %s (Error code %d): %s .\n",
|
|
pszFilename, status, nc_strerror(status) );
|
|
CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with GDALDataset own mutex
|
|
delete poDS;
|
|
CPLAcquireMutex(hNCMutex, 1000.0);
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Define dimensions */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->papszDimName.AddString( NCDF_DIMNAME_X );
|
|
status = nc_def_dim( poDS->cdfid, NCDF_DIMNAME_X, nXSize,
|
|
&(poDS->nXDimID) );
|
|
NCDF_ERR(status);
|
|
CPLDebug( "GDAL_netCDF", "status nc_def_dim( %d, %s, %d, -) got id %d",
|
|
poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID );
|
|
|
|
poDS->papszDimName.AddString( NCDF_DIMNAME_Y );
|
|
status = nc_def_dim( poDS->cdfid, NCDF_DIMNAME_Y, nYSize,
|
|
&(poDS->nYDimID) );
|
|
NCDF_ERR(status);
|
|
CPLDebug( "GDAL_netCDF", "status nc_def_dim( %d, %s, %d, -) got id %d",
|
|
poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID );
|
|
|
|
return poDS;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Create() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *
|
|
netCDFDataset::Create( const char * pszFilename,
|
|
int nXSize, int nYSize, int nBands,
|
|
GDALDataType eType,
|
|
char ** papszOptions )
|
|
{
|
|
netCDFDataset *poDS;
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"\n=====\nnetCDFDataset::Create( %s, ... )\n",
|
|
pszFilename );
|
|
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
poDS = netCDFDataset::CreateLL( pszFilename,
|
|
nXSize, nYSize, nBands,
|
|
papszOptions );
|
|
|
|
if ( ! poDS )
|
|
return NULL;
|
|
|
|
/* should we write signed or unsigned byte? */
|
|
/* TODO should this only be done in Create() */
|
|
poDS->bSignedData = TRUE;
|
|
const char *pszValue =
|
|
CSLFetchNameValue( papszOptions, "PIXELTYPE" );
|
|
if( pszValue == NULL )
|
|
pszValue = "";
|
|
if( eType == GDT_Byte && ( ! EQUAL(pszValue,"SIGNEDBYTE") ) )
|
|
poDS->bSignedData = FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Add Conventions, GDAL info and history */
|
|
/* -------------------------------------------------------------------- */
|
|
NCDFAddGDALHistory( poDS->cdfid, pszFilename, "", "Create" );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Define bands */
|
|
/* -------------------------------------------------------------------- */
|
|
for( int iBand = 1; iBand <= nBands; iBand++ )
|
|
{
|
|
poDS->SetBand( iBand, new netCDFRasterBand( poDS, eType, iBand,
|
|
poDS->bSignedData ) );
|
|
}
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"netCDFDataset::Create( %s, ... ) done",
|
|
pszFilename );
|
|
/* -------------------------------------------------------------------- */
|
|
/* Return same dataset */
|
|
/* -------------------------------------------------------------------- */
|
|
return( poDS );
|
|
|
|
}
|
|
|
|
|
|
template <class T>
|
|
CPLErr NCDFCopyBand( GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
|
|
int nXSize, int nYSize,
|
|
GDALProgressFunc pfnProgress, void * pProgressData )
|
|
{
|
|
GDALDataType eDT = poSrcBand->GetRasterDataType();
|
|
CPLErr eErr = CE_None;
|
|
T *patScanline = (T *) CPLMalloc( nXSize * sizeof(T) );
|
|
|
|
for( int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++ ) {
|
|
eErr = poSrcBand->RasterIO( GF_Read, 0, iLine, nXSize, 1,
|
|
patScanline, nXSize, 1, eDT,
|
|
0,0, NULL);
|
|
if ( eErr != CE_None )
|
|
CPLDebug( "GDAL_netCDF",
|
|
"NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
|
|
eErr );
|
|
else {
|
|
eErr = poDstBand->RasterIO( GF_Write, 0, iLine, nXSize, 1,
|
|
patScanline, nXSize, 1, eDT,
|
|
0,0, NULL);
|
|
if ( eErr != CE_None )
|
|
CPLDebug( "GDAL_netCDF",
|
|
"NCDFCopyBand(), poDstBand->RasterIO() returned error code %d",
|
|
eErr );
|
|
}
|
|
|
|
if ( ( nYSize>10 ) && ( iLine % (nYSize/10) == 1 ) ) {
|
|
if( !pfnProgress( 1.0*iLine/nYSize , NULL, pProgressData ) )
|
|
{
|
|
eErr = CE_Failure;
|
|
CPLError( CE_Failure, CPLE_UserInterrupt,
|
|
"User terminated CreateCopy()" );
|
|
}
|
|
}
|
|
}
|
|
|
|
CPLFree( patScanline );
|
|
|
|
pfnProgress( 1.0, NULL, pProgressData );
|
|
|
|
return eErr;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* CreateCopy() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset*
|
|
netCDFDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
|
|
CPL_UNUSED int bStrict, char ** papszOptions,
|
|
GDALProgressFunc pfnProgress, void * pProgressData )
|
|
{
|
|
netCDFDataset *poDS;
|
|
void *pScaledProgress;
|
|
GDALDataType eDT;
|
|
CPLErr eErr = CE_None;
|
|
int nBands, nXSize, nYSize;
|
|
double adfGeoTransform[6];
|
|
const char *pszWKT;
|
|
int iBand;
|
|
int status = NC_NOERR;
|
|
|
|
int nDim = 2;
|
|
int *panBandDimPos=NULL; // X, Y, Z postion in array
|
|
int *panBandZLev=NULL;
|
|
int *panDimIds=NULL;
|
|
int *panDimVarIds=NULL;
|
|
nc_type nVarType;
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
double dfTemp,dfTemp2;
|
|
netCDFRasterBand *poBand = NULL;
|
|
GDALRasterBand *poSrcBand = NULL;
|
|
GDALRasterBand *poDstBand = NULL;
|
|
int nBandID = -1;
|
|
|
|
CPLMutexHolderD(&hNCMutex);
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"\n=====\nnetCDFDataset::CreateCopy( %s, ... )\n",
|
|
pszFilename );
|
|
|
|
nBands = poSrcDS->GetRasterCount();
|
|
nXSize = poSrcDS->GetRasterXSize();
|
|
nYSize = poSrcDS->GetRasterYSize();
|
|
pszWKT = poSrcDS->GetProjectionRef();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check input bands for errors */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
if (nBands == 0)
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"NetCDF driver does not support source dataset with zero band.\n");
|
|
return NULL;
|
|
}
|
|
|
|
for( iBand=1; iBand <= nBands; iBand++ )
|
|
{
|
|
poSrcBand = poSrcDS->GetRasterBand( iBand );
|
|
eDT = poSrcBand->GetRasterDataType();
|
|
if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"NetCDF driver does not support source dataset with band of complex type.");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if( !pfnProgress( 0.0, NULL, pProgressData ) )
|
|
return NULL;
|
|
|
|
/* same as in Create() */
|
|
poDS = netCDFDataset::CreateLL( pszFilename,
|
|
nXSize, nYSize, nBands,
|
|
papszOptions );
|
|
if ( ! poDS )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Copy global metadata */
|
|
/* Add Conventions, GDAL info and history */
|
|
/* -------------------------------------------------------------------- */
|
|
CopyMetadata((void *) poSrcDS, poDS->cdfid, NC_GLOBAL, NULL, FALSE );
|
|
NCDFAddGDALHistory( poDS->cdfid, pszFilename,
|
|
poSrcDS->GetMetadataItem("NC_GLOBAL#history",""),
|
|
"CreateCopy" );
|
|
|
|
pfnProgress( 0.1, NULL, pProgressData );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for extra dimensions */
|
|
/* -------------------------------------------------------------------- */
|
|
char **papszExtraDimNames =
|
|
NCDFTokenizeArray( poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA","") );
|
|
char **papszExtraDimValues = NULL;
|
|
size_t nDimSize = -1;
|
|
size_t nDimSizeTot = 1;
|
|
if ( papszExtraDimNames != NULL && ( CSLCount( papszExtraDimNames )> 0 ) ) {
|
|
// first make sure dimensions lengths compatible with band count
|
|
// for ( int i=0; i<CSLCount( papszExtraDimNames ); i++ ) {
|
|
for ( int i=CSLCount( papszExtraDimNames )-1; i>=0; i-- ) {
|
|
sprintf( szTemp, "NETCDF_DIM_%s_DEF", papszExtraDimNames[i] );
|
|
papszExtraDimValues = NCDFTokenizeArray( poSrcDS->GetMetadataItem(szTemp,"") );
|
|
nDimSize = atol( papszExtraDimValues[0] );
|
|
CSLDestroy( papszExtraDimValues );
|
|
nDimSizeTot *= nDimSize;
|
|
}
|
|
if ( nDimSizeTot == (size_t)nBands ) {
|
|
nDim = 2 + CSLCount( papszExtraDimNames );
|
|
}
|
|
else {
|
|
// if nBands != #bands computed raise a warning
|
|
// just issue a debug message, because it was probably intentional
|
|
CPLDebug( "GDAL_netCDF",
|
|
"Warning: Number of bands (%d) is not compatible with dimensions "
|
|
"(total=%ld names=%s)", nBands, (long)nDimSizeTot,
|
|
poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA","") );
|
|
CSLDestroy( papszExtraDimNames );
|
|
papszExtraDimNames = NULL;
|
|
}
|
|
}
|
|
|
|
panDimIds = (int *)CPLCalloc( nDim, sizeof( int ) );
|
|
panBandDimPos = (int *) CPLCalloc( nDim, sizeof( int ) );
|
|
|
|
if ( nDim > 2 ) {
|
|
panBandZLev = (int *)CPLCalloc( nDim-2, sizeof( int ) );
|
|
panDimVarIds = (int *)CPLCalloc( nDim-2, sizeof( int ) );
|
|
|
|
/* define all dims */
|
|
for ( int i=CSLCount( papszExtraDimNames )-1; i>=0; i-- ) {
|
|
poDS->papszDimName.AddString( papszExtraDimNames[i] );
|
|
sprintf( szTemp, "NETCDF_DIM_%s_DEF", papszExtraDimNames[i] );
|
|
papszExtraDimValues = NCDFTokenizeArray( poSrcDS->GetMetadataItem(szTemp,"") );
|
|
nDimSize = atol( papszExtraDimValues[0] );
|
|
/* nc_type is an enum in netcdf-3, needs casting */
|
|
nVarType = (nc_type) atol( papszExtraDimValues[1] );
|
|
CSLDestroy( papszExtraDimValues );
|
|
panBandZLev[ i ] = nDimSize;
|
|
panBandDimPos[ i+2 ] = i; //Save Position of ZDim
|
|
|
|
/* define dim */
|
|
status = nc_def_dim( poDS->cdfid, papszExtraDimNames[i], nDimSize,
|
|
&(panDimIds[i]) );
|
|
NCDF_ERR(status);
|
|
|
|
/* define dim var */
|
|
int anDim[1];
|
|
anDim[0] = panDimIds[i];
|
|
status = nc_def_var( poDS->cdfid, papszExtraDimNames[i],
|
|
nVarType, 1, anDim,
|
|
&(panDimVarIds[i]) );
|
|
NCDF_ERR(status);
|
|
|
|
/* add dim metadata, using global var# items */
|
|
sprintf( szTemp, "%s#", papszExtraDimNames[i] );
|
|
CopyMetadata((void *) poSrcDS, poDS->cdfid, panDimVarIds[i], szTemp, FALSE );
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Copy GeoTransform and Projection */
|
|
/* -------------------------------------------------------------------- */
|
|
/* copy geolocation info */
|
|
if ( poSrcDS->GetMetadata("GEOLOCATION") != NULL )
|
|
poDS->SetMetadata( poSrcDS->GetMetadata("GEOLOCATION"), "GEOLOCATION" );
|
|
|
|
/* copy geotransform */
|
|
int bGotGeoTransform = FALSE;
|
|
eErr = poSrcDS->GetGeoTransform( adfGeoTransform );
|
|
if ( eErr == CE_None ) {
|
|
poDS->SetGeoTransform( adfGeoTransform );
|
|
/* disable AddProjectionVars() from being called */
|
|
bGotGeoTransform = TRUE;
|
|
poDS->bSetGeoTransform = FALSE;
|
|
}
|
|
|
|
/* copy projection */
|
|
if ( pszWKT ) {
|
|
poDS->SetProjection( pszWKT );
|
|
/* now we can call AddProjectionVars() directly */
|
|
poDS->bSetGeoTransform = bGotGeoTransform;
|
|
pScaledProgress = GDALCreateScaledProgress( 0.20, 0.50, pfnProgress,
|
|
pProgressData );
|
|
poDS->AddProjectionVars( GDALScaledProgress, pScaledProgress );
|
|
/* save X,Y dim positions */
|
|
panDimIds[nDim-1] = poDS->nXDimID;
|
|
panBandDimPos[0] = nDim-1;
|
|
panDimIds[nDim-2] = poDS->nYDimID;
|
|
panBandDimPos[1] = nDim-2;
|
|
GDALDestroyScaledProgress( pScaledProgress );
|
|
|
|
}
|
|
|
|
/* write extra dim values - after projection for optimization */
|
|
if ( nDim > 2 ) {
|
|
/* make sure we are in data mode */
|
|
( ( netCDFDataset * ) poDS )->SetDefineMode( FALSE );
|
|
for ( int i=CSLCount( papszExtraDimNames )-1; i>=0; i-- ) {
|
|
sprintf( szTemp, "NETCDF_DIM_%s_VALUES", papszExtraDimNames[i] );
|
|
if ( poSrcDS->GetMetadataItem( szTemp ) != NULL ) {
|
|
NCDFPut1DVar( poDS->cdfid, panDimVarIds[i],
|
|
poSrcDS->GetMetadataItem( szTemp ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
pfnProgress( 0.25, NULL, pProgressData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Define Bands */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
for( iBand=1; iBand <= nBands; iBand++ ) {
|
|
CPLDebug( "GDAL_netCDF", "creating band # %d/%d nDim = %d",
|
|
iBand, nBands, nDim );
|
|
|
|
char szBandName[ NC_MAX_NAME ];
|
|
char szLongName[ NC_MAX_NAME ];
|
|
const char *tmpMetadata;
|
|
int bSignedData = TRUE;
|
|
int bNoDataSet;
|
|
double dfNoDataValue;
|
|
|
|
poSrcBand = poSrcDS->GetRasterBand( iBand );
|
|
eDT = poSrcBand->GetRasterDataType();
|
|
|
|
/* Get var name from NETCDF_VARNAME */
|
|
tmpMetadata = poSrcBand->GetMetadataItem("NETCDF_VARNAME");
|
|
if( tmpMetadata != NULL) {
|
|
if( nBands > 1 && papszExtraDimNames == NULL )
|
|
sprintf(szBandName,"%s%d",tmpMetadata,iBand);
|
|
else strcpy( szBandName, tmpMetadata );
|
|
}
|
|
else
|
|
szBandName[0]='\0';
|
|
|
|
/* Get long_name from <var>#long_name */
|
|
sprintf(szLongName,"%s#%s",
|
|
poSrcBand->GetMetadataItem("NETCDF_VARNAME"),
|
|
CF_LNG_NAME);
|
|
tmpMetadata = poSrcDS->GetMetadataItem(szLongName);
|
|
if( tmpMetadata != NULL)
|
|
strcpy( szLongName, tmpMetadata);
|
|
else
|
|
szLongName[0]='\0';
|
|
|
|
if ( eDT == GDT_Byte ) {
|
|
/* GDAL defaults to unsigned bytes, but check if metadata says its
|
|
signed, as NetCDF can support this for certain formats. */
|
|
bSignedData = FALSE;
|
|
tmpMetadata = poSrcBand->GetMetadataItem("PIXELTYPE",
|
|
"IMAGE_STRUCTURE");
|
|
if ( tmpMetadata && EQUAL(tmpMetadata,"SIGNEDBYTE") )
|
|
bSignedData = TRUE;
|
|
}
|
|
|
|
if ( nDim > 2 )
|
|
poBand = new netCDFRasterBand( poDS, eDT, iBand,
|
|
bSignedData,
|
|
szBandName, szLongName,
|
|
nBandID, nDim, iBand-1,
|
|
panBandZLev, panBandDimPos,
|
|
panDimIds );
|
|
else
|
|
poBand = new netCDFRasterBand( poDS, eDT, iBand,
|
|
bSignedData,
|
|
szBandName, szLongName );
|
|
|
|
poDS->SetBand( iBand, poBand );
|
|
|
|
/* set nodata value, if any */
|
|
// poBand->SetNoDataValue( poSrcBand->GetNoDataValue(0) );
|
|
dfNoDataValue = poSrcBand->GetNoDataValue( &bNoDataSet );
|
|
if ( bNoDataSet ) {
|
|
CPLDebug( "GDAL_netCDF", "SetNoDataValue(%f) source", dfNoDataValue );
|
|
poBand->SetNoDataValue( dfNoDataValue );
|
|
}
|
|
|
|
/* Copy Metadata for band */
|
|
CopyMetadata( (void *) GDALGetRasterBand( poSrcDS, iBand ),
|
|
poDS->cdfid, poBand->nZId );
|
|
|
|
/* if more than 2D pass the first band's netcdf var ID to subsequent bands */
|
|
if ( nDim > 2 )
|
|
nBandID = poBand->nZId;
|
|
}
|
|
|
|
/* write projection variable to band variable */
|
|
poDS->AddGridMappingRef();
|
|
|
|
pfnProgress( 0.5, NULL, pProgressData );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write Bands */
|
|
/* -------------------------------------------------------------------- */
|
|
/* make sure we are in data mode */
|
|
poDS->SetDefineMode( FALSE );
|
|
|
|
dfTemp = dfTemp2 = 0.5;
|
|
|
|
eErr = CE_None;
|
|
|
|
for( iBand=1; iBand <= nBands && eErr == CE_None; iBand++ ) {
|
|
|
|
dfTemp2 = dfTemp + 0.4/nBands;
|
|
pScaledProgress =
|
|
GDALCreateScaledProgress( dfTemp, dfTemp2,
|
|
pfnProgress, pProgressData );
|
|
dfTemp = dfTemp2;
|
|
|
|
CPLDebug( "GDAL_netCDF", "copying band data # %d/%d ",
|
|
iBand,nBands );
|
|
|
|
poSrcBand = poSrcDS->GetRasterBand( iBand );
|
|
eDT = poSrcBand->GetRasterDataType();
|
|
|
|
poDstBand = poDS->GetRasterBand( iBand );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Copy Band data */
|
|
/* -------------------------------------------------------------------- */
|
|
if( eDT == GDT_Byte ) {
|
|
CPLDebug( "GDAL_netCDF", "GByte Band#%d", iBand );
|
|
eErr = NCDFCopyBand<GByte>( poSrcBand, poDstBand, nXSize, nYSize,
|
|
GDALScaledProgress, pScaledProgress );
|
|
}
|
|
else if( ( eDT == GDT_UInt16 ) || ( eDT == GDT_Int16 ) ) {
|
|
CPLDebug( "GDAL_netCDF", "GInt16 Band#%d", iBand );
|
|
eErr = NCDFCopyBand<GInt16>( poSrcBand, poDstBand, nXSize, nYSize,
|
|
GDALScaledProgress, pScaledProgress );
|
|
}
|
|
else if( (eDT == GDT_UInt32) || (eDT == GDT_Int32) ) {
|
|
CPLDebug( "GDAL_netCDF", "GInt16 Band#%d", iBand );
|
|
eErr = NCDFCopyBand<GInt32>( poSrcBand, poDstBand, nXSize, nYSize,
|
|
GDALScaledProgress, pScaledProgress );
|
|
}
|
|
else if( eDT == GDT_Float32 ) {
|
|
CPLDebug( "GDAL_netCDF", "float Band#%d", iBand);
|
|
eErr = NCDFCopyBand<float>( poSrcBand, poDstBand, nXSize, nYSize,
|
|
GDALScaledProgress, pScaledProgress );
|
|
}
|
|
else if( eDT == GDT_Float64 ) {
|
|
CPLDebug( "GDAL_netCDF", "double Band#%d", iBand);
|
|
eErr = NCDFCopyBand<double>( poSrcBand, poDstBand, nXSize, nYSize,
|
|
GDALScaledProgress, pScaledProgress );
|
|
}
|
|
else {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The NetCDF driver does not support GDAL data type %d",
|
|
eDT );
|
|
}
|
|
|
|
GDALDestroyScaledProgress( pScaledProgress );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Cleanup and close. */
|
|
/* -------------------------------------------------------------------- */
|
|
delete( poDS );
|
|
// CPLFree(pszProj4Defn );
|
|
|
|
if ( panDimIds )
|
|
CPLFree( panDimIds );
|
|
if( panBandDimPos )
|
|
CPLFree( panBandDimPos );
|
|
if ( panBandZLev )
|
|
CPLFree( panBandZLev );
|
|
if( panDimVarIds )
|
|
CPLFree( panDimVarIds );
|
|
if ( papszExtraDimNames )
|
|
CSLDestroy( papszExtraDimNames );
|
|
|
|
if (eErr != CE_None)
|
|
return NULL;
|
|
|
|
pfnProgress( 0.95, NULL, pProgressData );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Re-open dataset so we can return it. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS = (netCDFDataset *) GDALOpen( pszFilename, GA_ReadOnly );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* PAM cloning is disabled. See bug #4244. */
|
|
/* -------------------------------------------------------------------- */
|
|
// if( poDS )
|
|
// poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );
|
|
|
|
pfnProgress( 1.0, NULL, pProgressData );
|
|
|
|
return poDS;
|
|
}
|
|
|
|
/* note: some logic depends on bIsProjected and bIsGeoGraphic */
|
|
/* which may not be known when Create() is called, see AddProjectionVars() */
|
|
void
|
|
netCDFDataset::ProcessCreationOptions( )
|
|
{
|
|
const char *pszValue;
|
|
|
|
/* File format */
|
|
nFormat = NCDF_FORMAT_NC;
|
|
pszValue = CSLFetchNameValue( papszCreationOptions, "FORMAT" );
|
|
if ( pszValue != NULL ) {
|
|
if ( EQUAL( pszValue, "NC" ) ) {
|
|
nFormat = NCDF_FORMAT_NC;
|
|
}
|
|
#ifdef NETCDF_HAS_NC2
|
|
else if ( EQUAL( pszValue, "NC2" ) ) {
|
|
nFormat = NCDF_FORMAT_NC2;
|
|
}
|
|
#endif
|
|
#ifdef NETCDF_HAS_NC4
|
|
else if ( EQUAL( pszValue, "NC4" ) ) {
|
|
nFormat = NCDF_FORMAT_NC4;
|
|
}
|
|
else if ( EQUAL( pszValue, "NC4C" ) ) {
|
|
nFormat = NCDF_FORMAT_NC4C;
|
|
}
|
|
#endif
|
|
else {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"FORMAT=%s in not supported, using the default NC format.", pszValue );
|
|
}
|
|
}
|
|
|
|
/* compression only available for NC4 */
|
|
#ifdef NETCDF_HAS_NC4
|
|
|
|
/* COMPRESS option */
|
|
pszValue = CSLFetchNameValue( papszCreationOptions, "COMPRESS" );
|
|
if ( pszValue != NULL ) {
|
|
if ( EQUAL( pszValue, "NONE" ) ) {
|
|
nCompress = NCDF_COMPRESS_NONE;
|
|
}
|
|
else if ( EQUAL( pszValue, "DEFLATE" ) ) {
|
|
nCompress = NCDF_COMPRESS_DEFLATE;
|
|
if ( !((nFormat == NCDF_FORMAT_NC4) || (nFormat == NCDF_FORMAT_NC4C)) ) {
|
|
CPLError( CE_Warning, CPLE_IllegalArg,
|
|
"NOTICE: Format set to NC4C because compression is set to DEFLATE." );
|
|
nFormat = NCDF_FORMAT_NC4C;
|
|
}
|
|
}
|
|
else {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"COMPRESS=%s is not supported.", pszValue );
|
|
}
|
|
}
|
|
|
|
/* ZLEVEL option */
|
|
pszValue = CSLFetchNameValue( papszCreationOptions, "ZLEVEL" );
|
|
if( pszValue != NULL )
|
|
{
|
|
nZLevel = atoi( pszValue );
|
|
if (!(nZLevel >= 1 && nZLevel <= 9))
|
|
{
|
|
CPLError( CE_Warning, CPLE_IllegalArg,
|
|
"ZLEVEL=%s value not recognised, ignoring.",
|
|
pszValue );
|
|
nZLevel = NCDF_DEFLATE_LEVEL;
|
|
}
|
|
}
|
|
|
|
/* CHUNKING option */
|
|
bChunking = CSLFetchBoolean( papszCreationOptions, "CHUNKING", TRUE );
|
|
|
|
#endif
|
|
|
|
/* set nCreateMode based on nFormat */
|
|
switch ( nFormat ) {
|
|
#ifdef NETCDF_HAS_NC2
|
|
case NCDF_FORMAT_NC2:
|
|
nCreateMode = NC_CLOBBER|NC_64BIT_OFFSET;
|
|
break;
|
|
#endif
|
|
#ifdef NETCDF_HAS_NC4
|
|
case NCDF_FORMAT_NC4:
|
|
nCreateMode = NC_CLOBBER|NC_NETCDF4;
|
|
break;
|
|
case NCDF_FORMAT_NC4C:
|
|
nCreateMode = NC_CLOBBER|NC_NETCDF4|NC_CLASSIC_MODEL;
|
|
break;
|
|
#endif
|
|
case NCDF_FORMAT_NC:
|
|
default:
|
|
nCreateMode = NC_CLOBBER;
|
|
break;
|
|
}
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"file options: format=%d compress=%d zlevel=%d",
|
|
nFormat, nCompress, nZLevel );
|
|
|
|
}
|
|
|
|
int netCDFDataset::DefVarDeflate(
|
|
#ifdef NETCDF_HAS_NC4
|
|
int nVarId, int bChunkingArg
|
|
#else
|
|
CPL_UNUSED int nVarId, CPL_UNUSED int bChunkingArg
|
|
#endif
|
|
)
|
|
{
|
|
#ifdef NETCDF_HAS_NC4
|
|
if ( nCompress == NCDF_COMPRESS_DEFLATE ) {
|
|
// must set chunk size to avoid huge performace hit (set bChunkingArg=TRUE)
|
|
// perhaps another solution it to change the chunk cache?
|
|
// http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
|
|
// TODO make sure this is ok
|
|
CPLDebug( "GDAL_netCDF",
|
|
"DefVarDeflate( %d, %d ) nZlevel=%d",
|
|
nVarId, bChunkingArg, nZLevel );
|
|
|
|
status = nc_def_var_deflate(cdfid,nVarId,1,1,nZLevel);
|
|
NCDF_ERR(status);
|
|
|
|
if ( (status == NC_NOERR) && bChunkingArg && bChunking ) {
|
|
|
|
// set chunking to be 1 for all dims, except X dim
|
|
// size_t chunksize[] = { 1, (size_t)nRasterXSize };
|
|
size_t chunksize[ MAX_NC_DIMS ];
|
|
int nd;
|
|
nc_inq_varndims( cdfid, nVarId, &nd );
|
|
for( int i=0; i<nd; i++ ) chunksize[i] = (size_t)1;
|
|
chunksize[nd-1] = (size_t)nRasterXSize;
|
|
|
|
CPLDebug( "GDAL_netCDF",
|
|
"DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
|
|
(long)chunksize[0], (long)chunksize[1], (long)chunksize[nd-1], nd );
|
|
#ifdef NCDF_DEBUG
|
|
for( int i=0; i<nd; i++ )
|
|
CPLDebug( "GDAL_netCDF","DefVarDeflate() chunk[%d]=%ld", i, chunksize[i] );
|
|
#endif
|
|
|
|
status = nc_def_var_chunking( cdfid, nVarId,
|
|
NC_CHUNKED, chunksize );
|
|
NCDF_ERR(status);
|
|
}
|
|
else {
|
|
CPLDebug( "GDAL_netCDF",
|
|
"chunksize not set" );
|
|
}
|
|
return status;
|
|
}
|
|
#endif
|
|
return NC_NOERR;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* NCDFUnloadDriver() */
|
|
/************************************************************************/
|
|
|
|
static void NCDFUnloadDriver(CPL_UNUSED GDALDriver* poDriver)
|
|
{
|
|
if( hNCMutex != NULL )
|
|
CPLDestroyMutex(hNCMutex);
|
|
hNCMutex = NULL;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_netCDF() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_netCDF()
|
|
|
|
{
|
|
if (! GDAL_CHECK_VERSION("netCDF driver"))
|
|
return;
|
|
|
|
if( GDALGetDriverByName( "netCDF" ) == NULL )
|
|
{
|
|
GDALDriver *poDriver;
|
|
char szCreateOptions[3072];
|
|
|
|
poDriver = new GDALDriver( );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Build full creation option list. */
|
|
/* -------------------------------------------------------------------- */
|
|
sprintf( szCreateOptions, "%s",
|
|
"<CreationOptionList>"
|
|
" <Option name='FORMAT' type='string-select' default='NC'>"
|
|
" <Value>NC</Value>"
|
|
#ifdef NETCDF_HAS_NC2
|
|
" <Value>NC2</Value>"
|
|
#endif
|
|
#ifdef NETCDF_HAS_NC4
|
|
" <Value>NC4</Value>"
|
|
" <Value>NC4C</Value>"
|
|
#endif
|
|
" </Option>"
|
|
#ifdef NETCDF_HAS_NC4
|
|
" <Option name='COMPRESS' type='string-select' default='NONE'>"
|
|
" <Value>NONE</Value>"
|
|
" <Value>DEFLATE</Value>"
|
|
" </Option>"
|
|
" <Option name='ZLEVEL' type='int' description='DEFLATE compression level 1-9' default='1'/>"
|
|
#endif
|
|
" <Option name='WRITE_BOTTOMUP' type='boolean' default='YES'>"
|
|
" </Option>"
|
|
" <Option name='WRITE_GDAL_TAGS' type='boolean' default='YES'>"
|
|
" </Option>"
|
|
" <Option name='WRITE_LONLAT' type='string-select'>"
|
|
" <Value>YES</Value>"
|
|
" <Value>NO</Value>"
|
|
" <Value>IF_NEEDED</Value>"
|
|
" </Option>"
|
|
" <Option name='TYPE_LONLAT' type='string-select'>"
|
|
" <Value>float</Value>"
|
|
" <Value>double</Value>"
|
|
" </Option>"
|
|
" <Option name='PIXELTYPE' type='string-select' description='only used in Create()'>"
|
|
" <Value>DEFAULT</Value>"
|
|
" <Value>SIGNEDBYTE</Value>"
|
|
" </Option>"
|
|
" <Option name='CHUNKING' type='boolean' default='YES' description='define chunking when creating netcdf4 file'>"
|
|
" </Option>"
|
|
"</CreationOptionList>" );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set the driver details. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDriver->SetDescription( "netCDF" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"Network Common Data Format" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_netcdf.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "nc" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
|
|
szCreateOptions );
|
|
poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
|
|
|
|
/* make driver config and capabilities available */
|
|
poDriver->SetMetadataItem( "NETCDF_VERSION", nc_inq_libvers() );
|
|
poDriver->SetMetadataItem( "NETCDF_CONVENTIONS", NCDF_CONVENTIONS_CF );
|
|
#ifdef NETCDF_HAS_NC2
|
|
poDriver->SetMetadataItem( "NETCDF_HAS_NC2", "YES" );
|
|
#endif
|
|
#ifdef NETCDF_HAS_NC4
|
|
poDriver->SetMetadataItem( "NETCDF_HAS_NC4", "YES" );
|
|
#endif
|
|
#ifdef NETCDF_HAS_HDF4
|
|
poDriver->SetMetadataItem( "NETCDF_HAS_HDF4", "YES" );
|
|
#endif
|
|
#ifdef HAVE_HDF4
|
|
poDriver->SetMetadataItem( "GDAL_HAS_HDF4", "YES" );
|
|
#endif
|
|
#ifdef HAVE_HDF5
|
|
poDriver->SetMetadataItem( "GDAL_HAS_HDF5", "YES" );
|
|
#endif
|
|
|
|
/* set pfns and register driver */
|
|
poDriver->pfnOpen = netCDFDataset::Open;
|
|
poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
|
|
poDriver->pfnCreate = netCDFDataset::Create;
|
|
poDriver->pfnIdentify = netCDFDataset::Identify;
|
|
poDriver->pfnUnloadDriver = NCDFUnloadDriver;
|
|
|
|
GetGDALDriverManager( )->RegisterDriver( poDriver );
|
|
}
|
|
|
|
#ifdef NETCDF_PLUGIN
|
|
GDALRegister_GMT();
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* New functions */
|
|
/************************************************************************/
|
|
|
|
/* Test for GDAL version string >= target */
|
|
int NCDFIsGDALVersionGTE(const char* pszVersion, int nTarget)
|
|
{
|
|
int nVersion = 0;
|
|
int nVersions [] = {0,0,0,0};
|
|
char **papszTokens;
|
|
|
|
/* Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 " */
|
|
if ( pszVersion == NULL || EQUAL( pszVersion, "" ) )
|
|
return FALSE;
|
|
else if ( ! EQUALN("GDAL ", pszVersion, 5) )
|
|
return FALSE;
|
|
/* 2.0dev of 2011/12/29 has been later renamed as 1.10dev */
|
|
else if ( EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion) )
|
|
return nTarget <= GDAL_COMPUTE_VERSION(1,10,0);
|
|
else if ( EQUALN("GDAL 1.9dev", pszVersion,11 ) )
|
|
return nTarget <= 1900;
|
|
else if ( EQUALN("GDAL 1.8dev", pszVersion,11 ) )
|
|
return nTarget <= 1800;
|
|
|
|
papszTokens = CSLTokenizeString2( pszVersion+5, ".", 0 );
|
|
|
|
for ( int iToken = 0; papszTokens && papszTokens[iToken]; iToken++ ) {
|
|
nVersions[iToken] = atoi( papszTokens[iToken] );
|
|
}
|
|
if( nVersions[0] > 1 || nVersions[1] >= 10 )
|
|
nVersion = GDAL_COMPUTE_VERSION( nVersions[0], nVersions[1], nVersions[2] );
|
|
else
|
|
nVersion = nVersions[0]*1000 + nVersions[1]*100 +
|
|
nVersions[2]*10 + nVersions[3];
|
|
|
|
CSLDestroy( papszTokens );
|
|
return nTarget <= nVersion;
|
|
}
|
|
|
|
/* Add Conventions, GDAL version and history */
|
|
void NCDFAddGDALHistory( int fpImage,
|
|
const char * pszFilename, const char *pszOldHist,
|
|
const char * pszFunctionName)
|
|
{
|
|
char szTemp[NC_MAX_NAME];
|
|
|
|
nc_put_att_text( fpImage, NC_GLOBAL, "Conventions",
|
|
strlen(NCDF_CONVENTIONS_CF),
|
|
NCDF_CONVENTIONS_CF );
|
|
|
|
const char* pszNCDF_GDAL = GDALVersionInfo("--version");
|
|
nc_put_att_text( fpImage, NC_GLOBAL, "GDAL",
|
|
strlen(pszNCDF_GDAL), pszNCDF_GDAL );
|
|
|
|
/* Add history */
|
|
#ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
|
|
if ( ! EQUAL(GDALGetCmdLine(), "" ) )
|
|
strcpy( szTemp, GDALGetCmdLine() );
|
|
else
|
|
sprintf( szTemp, "GDAL %s( %s, ... )",pszFunctionName,pszFilename );
|
|
#else
|
|
sprintf( szTemp, "GDAL %s( %s, ... )",pszFunctionName,pszFilename );
|
|
#endif
|
|
|
|
NCDFAddHistory( fpImage, szTemp, pszOldHist );
|
|
|
|
}
|
|
|
|
/* code taken from cdo and libcdi, used for writing the history attribute */
|
|
//void cdoDefHistory(int fileID, char *histstring)
|
|
void NCDFAddHistory(int fpImage, const char *pszAddHist, const char *pszOldHist)
|
|
{
|
|
char strtime[32];
|
|
time_t tp;
|
|
struct tm *ltime;
|
|
|
|
char *pszNewHist = NULL;
|
|
size_t nNewHistSize = 0;
|
|
int disableHistory = FALSE;
|
|
int status;
|
|
|
|
/* Check pszOldHist - as if there was no previous history, it will be
|
|
a null pointer - if so set as empty. */
|
|
if (NULL == pszOldHist) {
|
|
pszOldHist = "";
|
|
}
|
|
|
|
tp = time(NULL);
|
|
if ( tp != -1 )
|
|
{
|
|
ltime = localtime(&tp);
|
|
(void) strftime(strtime, sizeof(strtime), "%a %b %d %H:%M:%S %Y: ", ltime);
|
|
}
|
|
|
|
// status = nc_get_att_text( fpImage, NC_GLOBAL,
|
|
// "history", pszOldHist );
|
|
// printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
|
|
|
|
nNewHistSize = strlen(pszOldHist)+strlen(strtime)+strlen(pszAddHist)+1+1;
|
|
pszNewHist = (char *) CPLMalloc(nNewHistSize * sizeof(char));
|
|
|
|
strcpy(pszNewHist, strtime);
|
|
strcat(pszNewHist, pszAddHist);
|
|
|
|
if ( disableHistory == FALSE && pszNewHist )
|
|
{
|
|
if ( ! EQUAL(pszOldHist,"") )
|
|
strcat(pszNewHist, "\n");
|
|
strcat(pszNewHist, pszOldHist);
|
|
}
|
|
|
|
status = nc_put_att_text( fpImage, NC_GLOBAL,
|
|
"history", strlen(pszNewHist),
|
|
pszNewHist );
|
|
NCDF_ERR(status);
|
|
|
|
CPLFree(pszNewHist);
|
|
}
|
|
|
|
|
|
int NCDFIsCfProjection( const char* pszProjection )
|
|
{
|
|
/* Find the appropriate mapping */
|
|
for (int iMap = 0; poNetcdfSRS_PT[iMap].WKT_SRS != NULL; iMap++ ) {
|
|
// printf("now at %d, proj=%s\n",i, poNetcdfSRS_PT[i].GDAL_SRS);
|
|
if ( EQUAL( pszProjection, poNetcdfSRS_PT[iMap].WKT_SRS ) ) {
|
|
if ( poNetcdfSRS_PT[iMap].mappings != NULL )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Write any needed projection attributes *
|
|
* poPROJCS: ptr to proj crd system
|
|
* pszProjection: name of projection system in GDAL WKT
|
|
* fpImage: open NetCDF file in writing mode
|
|
* NCDFVarID: NetCDF Var Id of proj system we're writing in to
|
|
*
|
|
* The function first looks for the oNetcdfSRS_PP mapping object
|
|
* that corresponds to the input projection name. If none is found
|
|
* the generic mapping is used. In the case of specific mappings,
|
|
* the driver looks for each attribute listed in the mapping object
|
|
* and then looks up the value within the OGR_SRSNode. In the case
|
|
* of the generic mapping, the lookup is reversed (projection params,
|
|
* then mapping). For more generic code, GDAL->NETCDF
|
|
* mappings and the associated value are saved in std::map objects.
|
|
*/
|
|
|
|
/* NOTE modifications by ET to combine the specific and generic mappings */
|
|
|
|
void NCDFWriteProjAttribs( const OGR_SRSNode *poPROJCS,
|
|
const char* pszProjection,
|
|
const int fpImage, const int NCDFVarID )
|
|
{
|
|
double dfStdP[2];
|
|
int bFoundStdP1=FALSE,bFoundStdP2=FALSE;
|
|
double dfValue=0.0;
|
|
const char *pszParamStr, *pszParamVal;
|
|
const std::string *pszNCDFAtt, *pszGDALAtt;
|
|
static const oNetcdfSRS_PP *poMap = NULL;
|
|
int nMapIndex = -1;
|
|
int bWriteVal = FALSE;
|
|
|
|
//Attribute <GDAL,NCDF> and Value <NCDF,value> mappings
|
|
std::map< std::string, std::string > oAttMap;
|
|
std::map< std::string, std::string >::iterator oAttIter;
|
|
std::map< std::string, double > oValMap;
|
|
std::map< std::string, double >::iterator oValIter, oValIter2;
|
|
//results to write
|
|
std::vector< std::pair<std::string,double> > oOutList;
|
|
|
|
/* Find the appropriate mapping */
|
|
for (int iMap = 0; poNetcdfSRS_PT[iMap].WKT_SRS != NULL; iMap++ ) {
|
|
if ( EQUAL( pszProjection, poNetcdfSRS_PT[iMap].WKT_SRS ) ) {
|
|
nMapIndex = iMap;
|
|
poMap = poNetcdfSRS_PT[iMap].mappings;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//ET TODO if projection name is not found, should we do something special?
|
|
if ( nMapIndex == -1 ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"projection name %s not found in the lookup tables!!!",
|
|
pszProjection);
|
|
}
|
|
/* if no mapping was found or assigned, set the generic one */
|
|
if ( !poMap ) {
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"projection name %s in not part of the CF standard, will not be supported by CF!",
|
|
pszProjection);
|
|
poMap = poGenericMappings;
|
|
}
|
|
|
|
/* initialize local map objects */
|
|
for ( int iMap = 0; poMap[iMap].WKT_ATT != NULL; iMap++ ) {
|
|
oAttMap[poMap[iMap].WKT_ATT] = poMap[iMap].CF_ATT;
|
|
}
|
|
|
|
for( int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++ ) {
|
|
|
|
const OGR_SRSNode *poNode;
|
|
|
|
poNode = poPROJCS->GetChild( iChild );
|
|
if( !EQUAL(poNode->GetValue(),"PARAMETER")
|
|
|| poNode->GetChildCount() != 2 )
|
|
continue;
|
|
pszParamStr = poNode->GetChild(0)->GetValue();
|
|
pszParamVal = poNode->GetChild(1)->GetValue();
|
|
|
|
oValMap[pszParamStr] = CPLAtof(pszParamVal);
|
|
}
|
|
|
|
/* Lookup mappings and fill output vector */
|
|
if ( poMap != poGenericMappings ) { /* specific mapping, loop over mapping values */
|
|
|
|
for ( oAttIter = oAttMap.begin(); oAttIter != oAttMap.end(); oAttIter++ ) {
|
|
|
|
pszGDALAtt = &(oAttIter->first);
|
|
pszNCDFAtt = &(oAttIter->second);
|
|
oValIter = oValMap.find( *pszGDALAtt );
|
|
|
|
if ( oValIter != oValMap.end() ) {
|
|
|
|
dfValue = oValIter->second;
|
|
bWriteVal = TRUE;
|
|
|
|
/* special case for PS (Polar Stereographic) grid
|
|
See comments in netcdfdataset.h for this projection. */
|
|
if ( EQUAL( SRS_PP_LATITUDE_OF_ORIGIN, pszGDALAtt->c_str() ) &&
|
|
EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC) ) {
|
|
double dfLatPole = 0.0;
|
|
if ( dfValue > 0.0) dfLatPole = 90.0;
|
|
else dfLatPole = -90.0;
|
|
oOutList.push_back( std::make_pair( std::string(CF_PP_LAT_PROJ_ORIGIN),
|
|
dfLatPole ) );
|
|
}
|
|
|
|
/* special case for LCC-1SP
|
|
See comments in netcdfdataset.h for this projection. */
|
|
else if ( EQUAL( SRS_PP_SCALE_FACTOR, pszGDALAtt->c_str() ) &&
|
|
EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) ) {
|
|
/* default is to not write as it is not CF-1 */
|
|
bWriteVal = FALSE;
|
|
/* test if there is no standard_parallel1 */
|
|
if ( oValMap.find( std::string(CF_PP_STD_PARALLEL_1) ) == oValMap.end() ) {
|
|
/* if scale factor != 1.0 write value for GDAL, but this is not supported by CF-1 */
|
|
if ( !CPLIsEqual(dfValue,1.0) ) {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"NetCDF driver export of LCC-1SP with scale factor != 1.0 "
|
|
"and no standard_parallel1 is not CF-1 (bug #3324).\n"
|
|
"Use the 2SP variant which is supported by CF." );
|
|
bWriteVal = TRUE;
|
|
}
|
|
/* else copy standard_parallel1 from latitude_of_origin, because scale_factor=1.0 */
|
|
else {
|
|
oValIter2 = oValMap.find( std::string(SRS_PP_LATITUDE_OF_ORIGIN) );
|
|
if (oValIter2 != oValMap.end() ) {
|
|
oOutList.push_back( std::make_pair( std::string(CF_PP_STD_PARALLEL_1),
|
|
oValIter2->second) );
|
|
}
|
|
else {
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"NetCDF driver export of LCC-1SP with no standard_parallel1 "
|
|
"and no latitude_of_origin is not suported (bug #3324).");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( bWriteVal )
|
|
oOutList.push_back( std::make_pair( *pszNCDFAtt, dfValue ) );
|
|
|
|
}
|
|
// else printf("NOT FOUND!!!\n");
|
|
}
|
|
|
|
}
|
|
else { /* generic mapping, loop over projected values */
|
|
|
|
for ( oValIter = oValMap.begin(); oValIter != oValMap.end(); oValIter++ ) {
|
|
|
|
pszGDALAtt = &(oValIter->first);
|
|
dfValue = oValIter->second;
|
|
|
|
oAttIter = oAttMap.find( *pszGDALAtt );
|
|
|
|
if ( oAttIter != oAttMap.end() ) {
|
|
oOutList.push_back( std::make_pair( oAttIter->second, dfValue ) );
|
|
}
|
|
/* for SRS_PP_SCALE_FACTOR write 2 mappings */
|
|
else if ( EQUAL(pszGDALAtt->c_str(), SRS_PP_SCALE_FACTOR) ) {
|
|
oOutList.push_back( std::make_pair( std::string(CF_PP_SCALE_FACTOR_MERIDIAN),
|
|
dfValue ) );
|
|
oOutList.push_back( std::make_pair( std::string(CF_PP_SCALE_FACTOR_ORIGIN),
|
|
dfValue ) );
|
|
}
|
|
/* if not found insert the GDAL name */
|
|
else {
|
|
oOutList.push_back( std::make_pair( *pszGDALAtt, dfValue ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write all the values that were found */
|
|
// std::vector< std::pair<std::string,double> >::reverse_iterator it;
|
|
// for (it = oOutList.rbegin(); it != oOutList.rend(); it++ ) {
|
|
std::vector< std::pair<std::string,double> >::iterator it;
|
|
for (it = oOutList.begin(); it != oOutList.end(); it++ ) {
|
|
pszParamVal = (it->first).c_str();
|
|
dfValue = it->second;
|
|
/* Handle the STD_PARALLEL attrib */
|
|
if( EQUAL( pszParamVal, CF_PP_STD_PARALLEL_1 ) ) {
|
|
bFoundStdP1 = TRUE;
|
|
dfStdP[0] = dfValue;
|
|
}
|
|
else if( EQUAL( pszParamVal, CF_PP_STD_PARALLEL_2 ) ) {
|
|
bFoundStdP2 = TRUE;
|
|
dfStdP[1] = dfValue;
|
|
}
|
|
else {
|
|
nc_put_att_double( fpImage, NCDFVarID, pszParamVal,
|
|
NC_DOUBLE, 1,&dfValue );
|
|
}
|
|
}
|
|
/* Now write the STD_PARALLEL attrib */
|
|
if ( bFoundStdP1 ) {
|
|
/* one value or equal values */
|
|
if ( !bFoundStdP2 || dfStdP[0] == dfStdP[1] ) {
|
|
nc_put_att_double( fpImage, NCDFVarID, CF_PP_STD_PARALLEL,
|
|
NC_DOUBLE, 1, &dfStdP[0] );
|
|
}
|
|
else { /* two values */
|
|
nc_put_att_double( fpImage, NCDFVarID, CF_PP_STD_PARALLEL,
|
|
NC_DOUBLE, 2, dfStdP );
|
|
}
|
|
}
|
|
}
|
|
|
|
CPLErr NCDFSafeStrcat(char** ppszDest, char* pszSrc, size_t* nDestSize)
|
|
{
|
|
/* Reallocate the data string until the content fits */
|
|
while(*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1)) {
|
|
(*nDestSize) *= 2;
|
|
*ppszDest = (char*) CPLRealloc((void*) *ppszDest, *nDestSize);
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld", (*nDestSize)/2, *nDestSize );
|
|
#endif
|
|
}
|
|
strcat(*ppszDest, pszSrc);
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
CPLErr NCDFSafeStrcpy(char** ppszDest, char* pszSrc, size_t* nDestSize)
|
|
{
|
|
/* Reallocate the data string until the content fits */
|
|
while(*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1)) {
|
|
(*nDestSize) *= 2;
|
|
*ppszDest = (char*) CPLRealloc((void*) *ppszDest, *nDestSize);
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "NCDFSafeStrcpy() resized str from %ld to %ld", (*nDestSize)/2, *nDestSize );
|
|
#endif
|
|
}
|
|
strcpy(*ppszDest, pszSrc);
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/* helper function for NCDFGetAttr() */
|
|
/* sets pdfValue to first value returned */
|
|
/* and if bSetPszValue=True sets pszValue with all attribute values */
|
|
/* pszValue is the responsibility of the caller and must be freed */
|
|
CPLErr NCDFGetAttr1( int nCdfId, int nVarId, const char *pszAttrName,
|
|
double *pdfValue, char **pszValue, int bSetPszValue )
|
|
{
|
|
nc_type nAttrType = NC_NAT;
|
|
size_t nAttrLen = 0;
|
|
size_t nAttrValueSize;
|
|
int status = 0; /*rename this */
|
|
size_t m;
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
char *pszAttrValue = NULL;
|
|
double dfValue = 0.0;
|
|
|
|
status = nc_inq_att( nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
|
|
#ifdef NCDF_DEBUG
|
|
CPLDebug( "GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName, nAttrLen, nAttrType );
|
|
#endif
|
|
|
|
/* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
|
|
nAttrValueSize = nAttrLen + 1;
|
|
if ( nAttrType != NC_CHAR && nAttrValueSize < 10 )
|
|
nAttrValueSize = 10;
|
|
if ( nAttrType == NC_DOUBLE && nAttrValueSize < 20 )
|
|
nAttrValueSize = 20;
|
|
pszAttrValue = (char *) CPLCalloc( nAttrValueSize, sizeof( char ));
|
|
*pszAttrValue = '\0';
|
|
|
|
if ( nAttrLen > 1 && nAttrType != NC_CHAR )
|
|
NCDFSafeStrcat(&pszAttrValue, (char *)"{", &nAttrValueSize);
|
|
|
|
switch (nAttrType) {
|
|
case NC_CHAR:
|
|
nc_get_att_text( nCdfId, nVarId, pszAttrName, pszAttrValue );
|
|
pszAttrValue[nAttrLen]='\0';
|
|
dfValue = 0.0;
|
|
break;
|
|
case NC_BYTE:
|
|
signed char *pscTemp;
|
|
pscTemp = (signed char *) CPLCalloc( nAttrLen, sizeof( signed char ) );
|
|
nc_get_att_schar( nCdfId, nVarId, pszAttrName, pscTemp );
|
|
dfValue = (double)pscTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
sprintf( szTemp, "%d,", pscTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
sprintf( szTemp, "%d", pscTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
CPLFree(pscTemp);
|
|
break;
|
|
#ifdef NETCDF_HAS_NC4
|
|
case NC_UBYTE:
|
|
unsigned char *pucTemp;
|
|
pucTemp = (unsigned char *) CPLCalloc( nAttrLen, sizeof( unsigned char ) );
|
|
nc_get_att_uchar( nCdfId, nVarId, pszAttrName, pucTemp );
|
|
dfValue = (double)pucTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
sprintf( szTemp, "%d,", pucTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
sprintf( szTemp, "%d", pucTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
CPLFree(pucTemp);
|
|
break;
|
|
#endif
|
|
case NC_SHORT:
|
|
short *psTemp;
|
|
psTemp = (short *) CPLCalloc( nAttrLen, sizeof( short ) );
|
|
nc_get_att_short( nCdfId, nVarId, pszAttrName, psTemp );
|
|
dfValue = (double)psTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
sprintf( szTemp, "%hd,", psTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
sprintf( szTemp, "%hd", psTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
CPLFree(psTemp);
|
|
break;
|
|
case NC_INT:
|
|
int *pnTemp;
|
|
pnTemp = (int *) CPLCalloc( nAttrLen, sizeof( int ) );
|
|
nc_get_att_int( nCdfId, nVarId, pszAttrName, pnTemp );
|
|
dfValue = (double)pnTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
sprintf( szTemp, "%d,", pnTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
sprintf( szTemp, "%d", pnTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
CPLFree(pnTemp);
|
|
break;
|
|
case NC_FLOAT:
|
|
float *pfTemp;
|
|
pfTemp = (float *) CPLCalloc( nAttrLen, sizeof( float ) );
|
|
nc_get_att_float( nCdfId, nVarId, pszAttrName, pfTemp );
|
|
dfValue = (double)pfTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
CPLsprintf( szTemp, "%.8g,", pfTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
CPLsprintf( szTemp, "%.8g", pfTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue,szTemp, &nAttrValueSize);
|
|
CPLFree(pfTemp);
|
|
break;
|
|
case NC_DOUBLE:
|
|
double *pdfTemp;
|
|
pdfTemp = (double *) CPLCalloc(nAttrLen, sizeof(double));
|
|
nc_get_att_double( nCdfId, nVarId, pszAttrName, pdfTemp );
|
|
dfValue = pdfTemp[0];
|
|
for(m=0; m < nAttrLen-1; m++) {
|
|
CPLsprintf( szTemp, "%.16g,", pdfTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
}
|
|
CPLsprintf( szTemp, "%.16g", pdfTemp[m] );
|
|
NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
|
|
CPLFree(pdfTemp);
|
|
break;
|
|
default:
|
|
CPLDebug( "GDAL_netCDF", "NCDFGetAttr unsupported type %d for attribute %s",
|
|
nAttrType,pszAttrName);
|
|
CPLFree( pszAttrValue );
|
|
pszAttrValue = NULL;
|
|
break;
|
|
}
|
|
|
|
if ( nAttrLen > 1 && nAttrType!= NC_CHAR )
|
|
NCDFSafeStrcat(&pszAttrValue, (char *)"}", &nAttrValueSize);
|
|
|
|
/* set return values */
|
|
if ( bSetPszValue == TRUE ) *pszValue = pszAttrValue;
|
|
else CPLFree ( pszAttrValue );
|
|
if ( pdfValue ) *pdfValue = dfValue;
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
|
|
/* sets pdfValue to first value found */
|
|
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
|
|
double *pdfValue )
|
|
{
|
|
return NCDFGetAttr1( nCdfId, nVarId, pszAttrName, pdfValue, NULL, FALSE );
|
|
}
|
|
|
|
|
|
/* pszValue is the responsibility of the caller and must be freed */
|
|
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
|
|
char **pszValue )
|
|
{
|
|
return NCDFGetAttr1( nCdfId, nVarId, pszAttrName, FALSE, pszValue, TRUE );
|
|
}
|
|
|
|
|
|
/* By default write NC_CHAR, but detect for int/float/double */
|
|
CPLErr NCDFPutAttr( int nCdfId, int nVarId,
|
|
const char *pszAttrName, const char *pszValue )
|
|
{
|
|
nc_type nAttrType = NC_CHAR;
|
|
nc_type nTmpAttrType = NC_CHAR;
|
|
size_t nAttrLen = 0;
|
|
int status = 0;
|
|
size_t i;
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
char *pszTemp = NULL;
|
|
char **papszValues = NULL;
|
|
|
|
int nValue = 0;
|
|
float fValue = 0.0f;
|
|
double dfValue = 0.0;
|
|
|
|
/* get the attribute values as tokens */
|
|
papszValues = NCDFTokenizeArray( pszValue );
|
|
if ( papszValues == NULL )
|
|
return CE_Failure;
|
|
|
|
nAttrLen = CSLCount(papszValues);
|
|
|
|
/* first detect type */
|
|
nAttrType = NC_CHAR;
|
|
for ( i=0; i<nAttrLen; i++ ) {
|
|
nTmpAttrType = NC_CHAR;
|
|
errno = 0;
|
|
nValue = strtol( papszValues[i], &pszTemp, 10 );
|
|
dfValue = (double) nValue;
|
|
/* test for int */
|
|
/* TODO test for Byte and short - can this be done safely? */
|
|
if ( (errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0) ) {
|
|
nTmpAttrType = NC_INT;
|
|
}
|
|
else {
|
|
/* test for double */
|
|
errno = 0;
|
|
dfValue = CPLStrtod( papszValues[i], &pszTemp );
|
|
if ( (errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0) ) {
|
|
/* test for float instead of double */
|
|
/* strtof() is C89, which is not available in MSVC */
|
|
/* see if we loose precision if we cast to float and write to char* */
|
|
fValue = (float)dfValue;
|
|
CPLsprintf( szTemp,"%.8g",fValue);
|
|
if ( EQUAL(szTemp, papszValues[i] ) )
|
|
nTmpAttrType = NC_FLOAT;
|
|
else
|
|
nTmpAttrType = NC_DOUBLE;
|
|
}
|
|
}
|
|
if ( nTmpAttrType > nAttrType )
|
|
nAttrType = nTmpAttrType;
|
|
}
|
|
|
|
/* now write the data */
|
|
if ( nAttrType == NC_CHAR ) {
|
|
status = nc_put_att_text( nCdfId, nVarId, pszAttrName,
|
|
strlen( pszValue ), pszValue );
|
|
NCDF_ERR(status);
|
|
}
|
|
else {
|
|
|
|
switch( nAttrType ) {
|
|
case NC_INT:
|
|
int *pnTemp;
|
|
pnTemp = (int *) CPLCalloc( nAttrLen, sizeof( int ) );
|
|
for(i=0; i < nAttrLen; i++) {
|
|
pnTemp[i] = strtol( papszValues[i], &pszTemp, 10 );
|
|
}
|
|
status = nc_put_att_int( nCdfId, nVarId, pszAttrName,
|
|
NC_INT, nAttrLen, pnTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pnTemp);
|
|
break;
|
|
case NC_FLOAT:
|
|
float *pfTemp;
|
|
pfTemp = (float *) CPLCalloc( nAttrLen, sizeof( float ) );
|
|
for(i=0; i < nAttrLen; i++) {
|
|
pfTemp[i] = (float)CPLStrtod( papszValues[i], &pszTemp );
|
|
}
|
|
status = nc_put_att_float( nCdfId, nVarId, pszAttrName,
|
|
NC_FLOAT, nAttrLen, pfTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pfTemp);
|
|
break;
|
|
case NC_DOUBLE:
|
|
double *pdfTemp;
|
|
pdfTemp = (double *) CPLCalloc( nAttrLen, sizeof( double ) );
|
|
for(i=0; i < nAttrLen; i++) {
|
|
pdfTemp[i] = CPLStrtod( papszValues[i], &pszTemp );
|
|
}
|
|
status = nc_put_att_double( nCdfId, nVarId, pszAttrName,
|
|
NC_DOUBLE, nAttrLen, pdfTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pdfTemp);
|
|
break;
|
|
default:
|
|
if ( papszValues ) CSLDestroy( papszValues );
|
|
return CE_Failure;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( papszValues ) CSLDestroy( papszValues );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
CPLErr NCDFGet1DVar( int nCdfId, int nVarId, char **pszValue )
|
|
{
|
|
nc_type nVarType = NC_NAT;
|
|
size_t nVarLen = 0;
|
|
int status = 0;
|
|
size_t m;
|
|
char szTemp[ NCDF_MAX_STR_LEN ];
|
|
char *pszVarValue = NULL;
|
|
size_t nVarValueSize;
|
|
int nVarDimId=-1;
|
|
size_t start[1], count[1];
|
|
|
|
/* get var information */
|
|
status = nc_inq_varndims( nCdfId, nVarId, &nVarDimId );
|
|
if ( status != NC_NOERR || nVarDimId != 1)
|
|
return CE_Failure;
|
|
status = nc_inq_vardimid( nCdfId, nVarId, &nVarDimId );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
status = nc_inq_vartype( nCdfId, nVarId, &nVarType );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
status = nc_inq_dimlen( nCdfId, nVarDimId, &nVarLen );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
start[0] = 0;
|
|
count[0] = nVarLen;
|
|
|
|
/* Allocate guaranteed minimum size */
|
|
nVarValueSize = NCDF_MAX_STR_LEN;
|
|
pszVarValue = (char *) CPLCalloc( nVarValueSize, sizeof( char ));
|
|
*pszVarValue = '\0';
|
|
|
|
if ( nVarLen > 1 && nVarType != NC_CHAR )
|
|
NCDFSafeStrcat(&pszVarValue, (char *)"{", &nVarValueSize);
|
|
|
|
switch (nVarType) {
|
|
case NC_CHAR:
|
|
nc_get_vara_text( nCdfId, nVarId, start, count, pszVarValue );
|
|
pszVarValue[nVarLen]='\0';
|
|
break;
|
|
/* TODO support NC_UBYTE */
|
|
case NC_BYTE:
|
|
signed char *pscTemp;
|
|
pscTemp = (signed char *) CPLCalloc( nVarLen, sizeof( signed char ) );
|
|
nc_get_vara_schar( nCdfId, nVarId, start, count, pscTemp );
|
|
for(m=0; m < nVarLen-1; m++) {
|
|
sprintf( szTemp, "%d,", pscTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
}
|
|
sprintf( szTemp, "%d", pscTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
CPLFree(pscTemp);
|
|
break;
|
|
case NC_SHORT:
|
|
short *psTemp;
|
|
psTemp = (short *) CPLCalloc( nVarLen, sizeof( short ) );
|
|
nc_get_vara_short( nCdfId, nVarId, start, count, psTemp );
|
|
for(m=0; m < nVarLen-1; m++) {
|
|
sprintf( szTemp, "%hd,", psTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
}
|
|
sprintf( szTemp, "%hd", psTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
CPLFree(psTemp);
|
|
break;
|
|
case NC_INT:
|
|
int *pnTemp;
|
|
pnTemp = (int *) CPLCalloc( nVarLen, sizeof( int ) );
|
|
nc_get_vara_int( nCdfId, nVarId, start, count, pnTemp );
|
|
for(m=0; m < nVarLen-1; m++) {
|
|
sprintf( szTemp, "%d,", pnTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
}
|
|
sprintf( szTemp, "%d", pnTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
CPLFree(pnTemp);
|
|
break;
|
|
case NC_FLOAT:
|
|
float *pfTemp;
|
|
pfTemp = (float *) CPLCalloc( nVarLen, sizeof( float ) );
|
|
nc_get_vara_float( nCdfId, nVarId, start, count, pfTemp );
|
|
for(m=0; m < nVarLen-1; m++) {
|
|
CPLsprintf( szTemp, "%.8g,", pfTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
}
|
|
CPLsprintf( szTemp, "%.8g", pfTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue,szTemp, &nVarValueSize);
|
|
CPLFree(pfTemp);
|
|
break;
|
|
case NC_DOUBLE:
|
|
double *pdfTemp;
|
|
pdfTemp = (double *) CPLCalloc(nVarLen, sizeof(double));
|
|
nc_get_vara_double( nCdfId, nVarId, start, count, pdfTemp );
|
|
for(m=0; m < nVarLen-1; m++) {
|
|
CPLsprintf( szTemp, "%.16g,", pdfTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
}
|
|
CPLsprintf( szTemp, "%.16g", pdfTemp[m] );
|
|
NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
|
|
CPLFree(pdfTemp);
|
|
break;
|
|
default:
|
|
CPLDebug( "GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
|
|
nVarType );
|
|
CPLFree( pszVarValue );
|
|
pszVarValue = NULL;
|
|
break;
|
|
}
|
|
|
|
if ( nVarLen > 1 && nVarType!= NC_CHAR )
|
|
NCDFSafeStrcat(&pszVarValue, (char *)"}", &nVarValueSize);
|
|
|
|
/* set return values */
|
|
*pszValue = pszVarValue;
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
CPLErr NCDFPut1DVar( int nCdfId, int nVarId, const char *pszValue )
|
|
{
|
|
nc_type nVarType = NC_CHAR;
|
|
size_t nVarLen = 0;
|
|
int status = 0;
|
|
size_t i;
|
|
char *pszTemp = NULL;
|
|
char **papszValues = NULL;
|
|
|
|
int nVarDimId=-1;
|
|
size_t start[1], count[1];
|
|
|
|
if ( EQUAL( pszValue, "" ) )
|
|
return CE_Failure;
|
|
|
|
/* get var information */
|
|
status = nc_inq_varndims( nCdfId, nVarId, &nVarDimId );
|
|
if ( status != NC_NOERR || nVarDimId != 1)
|
|
return CE_Failure;
|
|
status = nc_inq_vardimid( nCdfId, nVarId, &nVarDimId );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
status = nc_inq_vartype( nCdfId, nVarId, &nVarType );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
status = nc_inq_dimlen( nCdfId, nVarDimId, &nVarLen );
|
|
if ( status != NC_NOERR )
|
|
return CE_Failure;
|
|
start[0] = 0;
|
|
count[0] = nVarLen;
|
|
|
|
/* get the values as tokens */
|
|
papszValues = NCDFTokenizeArray( pszValue );
|
|
if ( papszValues == NULL )
|
|
return CE_Failure;
|
|
|
|
nVarLen = CSLCount(papszValues);
|
|
|
|
/* now write the data */
|
|
if ( nVarType == NC_CHAR ) {
|
|
status = nc_put_vara_text( nCdfId, nVarId, start, count,
|
|
pszValue );
|
|
NCDF_ERR(status);
|
|
}
|
|
else {
|
|
|
|
switch( nVarType ) {
|
|
/* TODO add other types */
|
|
case NC_INT:
|
|
int *pnTemp;
|
|
pnTemp = (int *) CPLCalloc( nVarLen, sizeof( int ) );
|
|
for(i=0; i < nVarLen; i++) {
|
|
pnTemp[i] = strtol( papszValues[i], &pszTemp, 10 );
|
|
}
|
|
status = nc_put_vara_int( nCdfId, nVarId, start, count, pnTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pnTemp);
|
|
break;
|
|
case NC_FLOAT:
|
|
float *pfTemp;
|
|
pfTemp = (float *) CPLCalloc( nVarLen, sizeof( float ) );
|
|
for(i=0; i < nVarLen; i++) {
|
|
pfTemp[i] = (float)CPLStrtod( papszValues[i], &pszTemp );
|
|
}
|
|
status = nc_put_vara_float( nCdfId, nVarId, start, count,
|
|
pfTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pfTemp);
|
|
break;
|
|
case NC_DOUBLE:
|
|
double *pdfTemp;
|
|
pdfTemp = (double *) CPLCalloc( nVarLen, sizeof( double ) );
|
|
for(i=0; i < nVarLen; i++) {
|
|
pdfTemp[i] = CPLStrtod( papszValues[i], &pszTemp );
|
|
}
|
|
status = nc_put_vara_double( nCdfId, nVarId, start, count,
|
|
pdfTemp );
|
|
NCDF_ERR(status);
|
|
CPLFree(pdfTemp);
|
|
break;
|
|
default:
|
|
if ( papszValues ) CSLDestroy( papszValues );
|
|
return CE_Failure;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( papszValues ) CSLDestroy( papszValues );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* GetDefaultNoDataValue() */
|
|
/************************************************************************/
|
|
|
|
double NCDFGetDefaultNoDataValue( int nVarType )
|
|
|
|
{
|
|
double dfNoData = 0.0;
|
|
|
|
switch( nVarType ) {
|
|
case NC_BYTE:
|
|
#ifdef NETCDF_HAS_NC4
|
|
case NC_UBYTE:
|
|
#endif
|
|
/* don't do default fill-values for bytes, too risky */
|
|
dfNoData = 0.0;
|
|
break;
|
|
case NC_CHAR:
|
|
dfNoData = NC_FILL_CHAR;
|
|
break;
|
|
case NC_SHORT:
|
|
dfNoData = NC_FILL_SHORT;
|
|
break;
|
|
case NC_INT:
|
|
dfNoData = NC_FILL_INT;
|
|
break;
|
|
case NC_FLOAT:
|
|
dfNoData = NC_FILL_FLOAT;
|
|
break;
|
|
case NC_DOUBLE:
|
|
dfNoData = NC_FILL_DOUBLE;
|
|
break;
|
|
default:
|
|
dfNoData = 0.0;
|
|
break;
|
|
}
|
|
|
|
return dfNoData;
|
|
}
|
|
|
|
|
|
int NCDFDoesVarContainAttribVal( int nCdfId,
|
|
const char ** papszAttribNames,
|
|
const char ** papszAttribValues,
|
|
int nVarId,
|
|
const char * pszVarName,
|
|
int bStrict=TRUE )
|
|
{
|
|
char *pszTemp = NULL;
|
|
int bFound = FALSE;
|
|
|
|
if ( (nVarId == -1) && (pszVarName != NULL) )
|
|
nc_inq_varid( nCdfId, pszVarName, &nVarId );
|
|
|
|
if ( nVarId == -1 ) return -1;
|
|
|
|
for( int i=0; !bFound && i<CSLCount((char**)papszAttribNames); i++ ) {
|
|
if ( NCDFGetAttr( nCdfId, nVarId, papszAttribNames[i], &pszTemp )
|
|
== CE_None && pszTemp != NULL ) {
|
|
if ( bStrict ) {
|
|
if ( EQUAL( pszTemp, papszAttribValues[i] ) )
|
|
bFound=TRUE;
|
|
}
|
|
else {
|
|
if ( EQUALN( pszTemp, papszAttribValues[i], strlen(papszAttribValues[i]) ) )
|
|
bFound=TRUE;
|
|
}
|
|
CPLFree( pszTemp );
|
|
}
|
|
}
|
|
return bFound;
|
|
}
|
|
|
|
int NCDFDoesVarContainAttribVal2( int nCdfId,
|
|
const char * papszAttribName,
|
|
const char ** papszAttribValues,
|
|
int nVarId,
|
|
const char * pszVarName,
|
|
int bStrict=TRUE )
|
|
{
|
|
char *pszTemp = NULL;
|
|
int bFound = FALSE;
|
|
|
|
if ( (nVarId == -1) && (pszVarName != NULL) )
|
|
nc_inq_varid( nCdfId, pszVarName, &nVarId );
|
|
|
|
if ( nVarId == -1 ) return -1;
|
|
|
|
if ( NCDFGetAttr( nCdfId, nVarId, papszAttribName, &pszTemp )
|
|
!= CE_None || pszTemp == NULL ) return FALSE;
|
|
|
|
for( int i=0; !bFound && i<CSLCount((char**)papszAttribValues); i++ ) {
|
|
if ( bStrict ) {
|
|
if ( EQUAL( pszTemp, papszAttribValues[i] ) )
|
|
bFound=TRUE;
|
|
}
|
|
else {
|
|
if ( EQUALN( pszTemp, papszAttribValues[i], strlen(papszAttribValues[i]) ) )
|
|
bFound=TRUE;
|
|
}
|
|
}
|
|
|
|
CPLFree( pszTemp );
|
|
|
|
return bFound;
|
|
}
|
|
|
|
int NCDFEqual( const char * papszName, const char ** papszValues )
|
|
{
|
|
int bFound = FALSE;
|
|
|
|
if ( papszName == NULL || EQUAL(papszName,"") )
|
|
return FALSE;
|
|
|
|
for( int i=0; i<CSLCount((char**)papszValues); i++ ) {
|
|
if( EQUAL( papszName, papszValues[i] ) )
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
/* test that a variable is longitude/latitude coordinate, following CF 4.1 and 4.2 */
|
|
int NCDFIsVarLongitude( int nCdfId, int nVarId,
|
|
const char * pszVarName )
|
|
{
|
|
/* check for matching attributes */
|
|
int bVal = NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFLongitudeAttribNames,
|
|
papszCFLongitudeAttribValues,
|
|
nVarId, pszVarName );
|
|
/* if not found using attributes then check using var name */
|
|
/* unless GDAL_NETCDF_VERIFY_DIMS=STRICT */
|
|
if ( bVal == -1 ) {
|
|
if ( ! EQUAL( CPLGetConfigOption( "GDAL_NETCDF_VERIFY_DIMS", "YES" ),
|
|
"STRICT" ) )
|
|
bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames );
|
|
else
|
|
bVal = FALSE;
|
|
}
|
|
return bVal;
|
|
}
|
|
|
|
int NCDFIsVarLatitude( int nCdfId, int nVarId, const char * pszVarName )
|
|
{
|
|
int bVal = NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFLatitudeAttribNames,
|
|
papszCFLatitudeAttribValues,
|
|
nVarId, pszVarName );
|
|
if ( bVal == -1 ) {
|
|
if ( ! EQUAL( CPLGetConfigOption( "GDAL_NETCDF_VERIFY_DIMS", "YES" ),
|
|
"STRICT" ) )
|
|
bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames );
|
|
else
|
|
bVal = FALSE;
|
|
}
|
|
return bVal;
|
|
}
|
|
|
|
int NCDFIsVarProjectionX( int nCdfId, int nVarId, const char * pszVarName )
|
|
{
|
|
int bVal = NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFProjectionXAttribNames,
|
|
papszCFProjectionXAttribValues,
|
|
nVarId, pszVarName );
|
|
if ( bVal == -1 ) {
|
|
if ( ! EQUAL( CPLGetConfigOption( "GDAL_NETCDF_VERIFY_DIMS", "YES" ),
|
|
"STRICT" ) )
|
|
bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames );
|
|
else
|
|
bVal = FALSE;
|
|
|
|
}
|
|
return bVal;
|
|
}
|
|
|
|
int NCDFIsVarProjectionY( int nCdfId, int nVarId, const char * pszVarName )
|
|
{
|
|
int bVal = NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFProjectionYAttribNames,
|
|
papszCFProjectionYAttribValues,
|
|
nVarId, pszVarName );
|
|
if ( bVal == -1 ) {
|
|
if ( ! EQUAL( CPLGetConfigOption( "GDAL_NETCDF_VERIFY_DIMS", "YES" ),
|
|
"STRICT" ) )
|
|
bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames );
|
|
else
|
|
bVal = FALSE;
|
|
}
|
|
return bVal;
|
|
}
|
|
|
|
/* test that a variable is a vertical coordinate, following CF 4.3 */
|
|
int NCDFIsVarVerticalCoord( int nCdfId, int nVarId,
|
|
const char * pszVarName )
|
|
{
|
|
/* check for matching attributes */
|
|
if ( NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFVerticalAttribNames,
|
|
papszCFVerticalAttribValues,
|
|
nVarId, pszVarName ) == TRUE )
|
|
return TRUE;
|
|
/* check for matching units */
|
|
else if ( NCDFDoesVarContainAttribVal2( nCdfId,
|
|
CF_UNITS,
|
|
papszCFVerticalUnitsValues,
|
|
nVarId, pszVarName ) == TRUE )
|
|
return TRUE;
|
|
/* check for matching standard name */
|
|
else if ( NCDFDoesVarContainAttribVal2( nCdfId,
|
|
CF_STD_NAME,
|
|
papszCFVerticalStandardNameValues,
|
|
nVarId, pszVarName ) == TRUE )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* test that a variable is a time coordinate, following CF 4.4 */
|
|
int NCDFIsVarTimeCoord( int nCdfId, int nVarId,
|
|
const char * pszVarName )
|
|
{
|
|
/* check for matching attributes */
|
|
if ( NCDFDoesVarContainAttribVal( nCdfId,
|
|
papszCFTimeAttribNames,
|
|
papszCFTimeAttribValues,
|
|
nVarId, pszVarName ) == TRUE )
|
|
return TRUE;
|
|
/* check for matching units */
|
|
else if ( NCDFDoesVarContainAttribVal2( nCdfId,
|
|
CF_UNITS,
|
|
papszCFTimeUnitsValues,
|
|
nVarId, pszVarName, FALSE ) == TRUE )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse a string, and return as a string list */
|
|
/* if it an array of the form {a,b} then tokenize it */
|
|
/* else return a copy */
|
|
char **NCDFTokenizeArray( const char *pszValue )
|
|
{
|
|
char **papszValues = NULL;
|
|
char *pszTemp = NULL;
|
|
int nLen = 0;
|
|
|
|
if ( pszValue==NULL || EQUAL( pszValue, "" ) )
|
|
return NULL;
|
|
|
|
nLen = strlen(pszValue);
|
|
|
|
if ( ( pszValue[0] == '{' ) && ( pszValue[nLen-1] == '}' ) ) {
|
|
pszTemp = (char *) CPLCalloc(nLen-2,sizeof(char*));
|
|
strncpy( pszTemp, pszValue+1, nLen-2);
|
|
pszTemp[nLen-2] = '\0';
|
|
papszValues = CSLTokenizeString2( pszTemp, ",", CSLT_ALLOWEMPTYTOKENS );
|
|
CPLFree( pszTemp);
|
|
}
|
|
else {
|
|
papszValues = (char**) CPLCalloc(2,sizeof(char*));
|
|
papszValues[0] = CPLStrdup( pszValue );
|
|
papszValues[1] = NULL;
|
|
}
|
|
|
|
return papszValues;
|
|
}
|