mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
1106 lines
39 KiB
C++
1106 lines
39 KiB
C++
/******************************************************************************
|
|
* $Id: hf2dataset.cpp 28053 2014-12-04 09:31:07Z rouault $
|
|
*
|
|
* Project: HF2 driver
|
|
* Purpose: GDALDataset driver for HF2/HFZ dataset.
|
|
* Author: Even Rouault, <even dot rouault at mines dash paris dot org>
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2010-2012, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "cpl_string.h"
|
|
#include "gdal_pam.h"
|
|
#include "ogr_spatialref.h"
|
|
|
|
CPL_CVSID("$Id: hf2dataset.cpp 28053 2014-12-04 09:31:07Z rouault $");
|
|
|
|
CPL_C_START
|
|
void GDALRegister_HF2(void);
|
|
CPL_C_END
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* HF2Dataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class HF2RasterBand;
|
|
|
|
class HF2Dataset : public GDALPamDataset
|
|
{
|
|
friend class HF2RasterBand;
|
|
|
|
VSILFILE *fp;
|
|
double adfGeoTransform[6];
|
|
char *pszWKT;
|
|
vsi_l_offset *panBlockOffset;
|
|
|
|
int nTileSize;
|
|
int bHasLoaderBlockMap;
|
|
int LoadBlockMap();
|
|
|
|
public:
|
|
HF2Dataset();
|
|
virtual ~HF2Dataset();
|
|
|
|
virtual CPLErr GetGeoTransform( double * );
|
|
virtual const char* GetProjectionRef();
|
|
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
static int Identify( GDALOpenInfo * );
|
|
static GDALDataset *CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
|
|
int bStrict, char ** papszOptions,
|
|
GDALProgressFunc pfnProgress, void * pProgressData );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* HF2RasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class HF2RasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class HF2Dataset;
|
|
|
|
float* pafBlockData;
|
|
int nLastBlockYOff;
|
|
|
|
public:
|
|
|
|
HF2RasterBand( HF2Dataset *, int, GDALDataType );
|
|
~HF2RasterBand();
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
};
|
|
|
|
|
|
/************************************************************************/
|
|
/* HF2RasterBand() */
|
|
/************************************************************************/
|
|
|
|
HF2RasterBand::HF2RasterBand( HF2Dataset *poDS, int nBand, GDALDataType eDT )
|
|
|
|
{
|
|
this->poDS = poDS;
|
|
this->nBand = nBand;
|
|
|
|
eDataType = eDT;
|
|
|
|
nBlockXSize = poDS->nTileSize;
|
|
nBlockYSize = 1;
|
|
|
|
pafBlockData = NULL;
|
|
nLastBlockYOff = -1;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~HF2RasterBand() */
|
|
/************************************************************************/
|
|
|
|
HF2RasterBand::~HF2RasterBand()
|
|
{
|
|
CPLFree(pafBlockData);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr HF2RasterBand::IReadBlock( int nBlockXOff, int nLineYOff,
|
|
void * pImage )
|
|
|
|
{
|
|
HF2Dataset *poGDS = (HF2Dataset *) poDS;
|
|
|
|
int nXBlocks = (nRasterXSize + nBlockXSize - 1) / nBlockXSize;
|
|
int nYBlocks = (nRasterYSize + nBlockXSize - 1) / nBlockXSize;
|
|
|
|
if (!poGDS->LoadBlockMap())
|
|
return CE_Failure;
|
|
|
|
if (pafBlockData == NULL)
|
|
{
|
|
pafBlockData = (float*)VSIMalloc3(nXBlocks * sizeof(float), poGDS->nTileSize, poGDS->nTileSize);
|
|
if (pafBlockData == NULL)
|
|
return CE_Failure;
|
|
}
|
|
|
|
nLineYOff = nRasterYSize - 1 - nLineYOff;
|
|
|
|
int nBlockYOff = nLineYOff / nBlockXSize;
|
|
int nYOffInTile = nLineYOff % nBlockXSize;
|
|
|
|
if (nBlockYOff != nLastBlockYOff)
|
|
{
|
|
nLastBlockYOff = nBlockYOff;
|
|
|
|
memset(pafBlockData, 0, nXBlocks * sizeof(float) * nBlockXSize * nBlockXSize);
|
|
|
|
/* 4 * nBlockXSize is the upper bound */
|
|
void* pabyData = CPLMalloc( 4 * nBlockXSize );
|
|
|
|
int nxoff;
|
|
for(nxoff = 0; nxoff < nXBlocks; nxoff++)
|
|
{
|
|
VSIFSeekL(poGDS->fp, poGDS->panBlockOffset[(nYBlocks - 1 - nBlockYOff) * nXBlocks + nxoff], SEEK_SET);
|
|
float fScale, fOff;
|
|
VSIFReadL(&fScale, 4, 1, poGDS->fp);
|
|
VSIFReadL(&fOff, 4, 1, poGDS->fp);
|
|
CPL_LSBPTR32(&fScale);
|
|
CPL_LSBPTR32(&fOff);
|
|
|
|
int nTileWidth = MIN(nBlockXSize, nRasterXSize - nxoff * nBlockXSize);
|
|
int nTileHeight = MIN(nBlockXSize, nRasterYSize - nBlockYOff * nBlockXSize);
|
|
|
|
int j;
|
|
for(j=0;j<nTileHeight;j++)
|
|
{
|
|
GByte nWordSize;
|
|
VSIFReadL(&nWordSize, 1, 1, poGDS->fp);
|
|
if (nWordSize != 1 && nWordSize != 2 && nWordSize != 4)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unexpected word size : %d", (int)nWordSize);
|
|
break;
|
|
}
|
|
|
|
GInt32 nVal;
|
|
VSIFReadL(&nVal, 4, 1, poGDS->fp);
|
|
CPL_LSBPTR32(&nVal);
|
|
VSIFReadL(pabyData, nWordSize * (nTileWidth - 1), 1, poGDS->fp);
|
|
#if defined(CPL_MSB)
|
|
if (nWordSize > 1)
|
|
GDALSwapWords(pabyData, nWordSize, nTileWidth - 1, nWordSize);
|
|
#endif
|
|
|
|
pafBlockData[nxoff * nBlockXSize * nBlockXSize + j * nBlockXSize + 0] = nVal * fScale + fOff;
|
|
int i;
|
|
for(i=1;i<nTileWidth;i++)
|
|
{
|
|
if (nWordSize == 1)
|
|
nVal += ((char*)pabyData)[i-1];
|
|
else if (nWordSize == 2)
|
|
nVal += ((GInt16*)pabyData)[i-1];
|
|
else
|
|
nVal += ((GInt32*)pabyData)[i-1];
|
|
pafBlockData[nxoff * nBlockXSize * nBlockXSize + j * nBlockXSize + i] = nVal * fScale + fOff;
|
|
}
|
|
}
|
|
}
|
|
|
|
CPLFree(pabyData);
|
|
}
|
|
|
|
int nTileWidth = MIN(nBlockXSize, nRasterXSize - nBlockXOff * nBlockXSize);
|
|
memcpy(pImage, pafBlockData + nBlockXOff * nBlockXSize * nBlockXSize +
|
|
nYOffInTile * nBlockXSize,
|
|
nTileWidth * sizeof(float));
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~HF2Dataset() */
|
|
/************************************************************************/
|
|
|
|
HF2Dataset::HF2Dataset()
|
|
{
|
|
fp = NULL;
|
|
pszWKT = NULL;
|
|
panBlockOffset = NULL;
|
|
adfGeoTransform[0] = 0;
|
|
adfGeoTransform[1] = 1;
|
|
adfGeoTransform[2] = 0;
|
|
adfGeoTransform[3] = 0;
|
|
adfGeoTransform[4] = 0;
|
|
adfGeoTransform[5] = 1;
|
|
bHasLoaderBlockMap = FALSE;
|
|
nTileSize = 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~HF2Dataset() */
|
|
/************************************************************************/
|
|
|
|
HF2Dataset::~HF2Dataset()
|
|
|
|
{
|
|
FlushCache();
|
|
CPLFree(pszWKT);
|
|
CPLFree(panBlockOffset);
|
|
if (fp)
|
|
VSIFCloseL(fp);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* LoadBlockMap() */
|
|
/************************************************************************/
|
|
|
|
int HF2Dataset::LoadBlockMap()
|
|
{
|
|
if (bHasLoaderBlockMap)
|
|
return panBlockOffset != NULL;
|
|
|
|
bHasLoaderBlockMap = TRUE;
|
|
|
|
int nXBlocks = (nRasterXSize + nTileSize - 1) / nTileSize;
|
|
int nYBlocks = (nRasterYSize + nTileSize - 1) / nTileSize;
|
|
panBlockOffset = (vsi_l_offset*) VSIMalloc3(sizeof(vsi_l_offset), nXBlocks, nYBlocks);
|
|
if (panBlockOffset == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
int i, j;
|
|
for(j = 0; j < nYBlocks; j++)
|
|
{
|
|
for(i = 0; i < nXBlocks; i++)
|
|
{
|
|
vsi_l_offset nOff = VSIFTellL(fp);
|
|
panBlockOffset[(nYBlocks - 1 - j) * nXBlocks + i] = nOff;
|
|
//VSIFSeekL(fp, 4 + 4, SEEK_CUR);
|
|
float fScale, fOff;
|
|
VSIFReadL(&fScale, 4, 1, fp);
|
|
VSIFReadL(&fOff, 4, 1, fp);
|
|
CPL_LSBPTR32(&fScale);
|
|
CPL_LSBPTR32(&fOff);
|
|
//printf("fScale = %f, fOff = %f\n", fScale, fOff);
|
|
int k;
|
|
int nCols = MIN(nTileSize, nRasterXSize - nTileSize *i);
|
|
int nLines = MIN(nTileSize, nRasterYSize - nTileSize *j);
|
|
for(k = 0; k < nLines; k++)
|
|
{
|
|
GByte nWordSize;
|
|
VSIFReadL(&nWordSize, 1, 1, fp);
|
|
//printf("nWordSize=%d\n", nWordSize);
|
|
if (nWordSize == 1 || nWordSize == 2 || nWordSize == 4)
|
|
VSIFSeekL(fp, 4 + nWordSize * (nCols - 1), SEEK_CUR);
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Got unexpected byte depth (%d) for block (%d, %d) line %d",
|
|
(int)nWordSize, i, j, k);
|
|
VSIFree(panBlockOffset);
|
|
panBlockOffset = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetProjectionRef() */
|
|
/************************************************************************/
|
|
|
|
const char* HF2Dataset::GetProjectionRef()
|
|
{
|
|
if (pszWKT)
|
|
return pszWKT;
|
|
return GDALPamDataset::GetProjectionRef();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int HF2Dataset::Identify( GDALOpenInfo * poOpenInfo)
|
|
{
|
|
|
|
GDALOpenInfo* poOpenInfoToDelete = NULL;
|
|
/* GZipped .hf2 files are common, so automagically open them */
|
|
/* if the /vsigzip/ has not been explicitly passed */
|
|
CPLString osFilename(poOpenInfo->pszFilename);
|
|
if ((EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "hfz") ||
|
|
(strlen(poOpenInfo->pszFilename) > 6 &&
|
|
EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6, "hf2.gz"))) &&
|
|
!EQUALN(poOpenInfo->pszFilename, "/vsigzip/", 9))
|
|
{
|
|
osFilename = "/vsigzip/";
|
|
osFilename += poOpenInfo->pszFilename;
|
|
poOpenInfo = poOpenInfoToDelete =
|
|
new GDALOpenInfo(osFilename.c_str(), GA_ReadOnly,
|
|
poOpenInfo->GetSiblingFiles());
|
|
}
|
|
|
|
if (poOpenInfo->nHeaderBytes < 28)
|
|
{
|
|
delete poOpenInfoToDelete;
|
|
return FALSE;
|
|
}
|
|
|
|
if (memcmp(poOpenInfo->pabyHeader, "HF2\0\0\0\0", 6) != 0)
|
|
{
|
|
delete poOpenInfoToDelete;
|
|
return FALSE;
|
|
}
|
|
|
|
delete poOpenInfoToDelete;
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *HF2Dataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
CPLString osOriginalFilename(poOpenInfo->pszFilename);
|
|
|
|
if (!Identify(poOpenInfo))
|
|
return NULL;
|
|
|
|
GDALOpenInfo* poOpenInfoToDelete = NULL;
|
|
/* GZipped .hf2 files are common, so automagically open them */
|
|
/* if the /vsigzip/ has not been explicitly passed */
|
|
CPLString osFilename(poOpenInfo->pszFilename);
|
|
if ((EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "hfz") ||
|
|
(strlen(poOpenInfo->pszFilename) > 6 &&
|
|
EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 6, "hf2.gz"))) &&
|
|
!EQUALN(poOpenInfo->pszFilename, "/vsigzip/", 9))
|
|
{
|
|
osFilename = "/vsigzip/";
|
|
osFilename += poOpenInfo->pszFilename;
|
|
poOpenInfo = poOpenInfoToDelete =
|
|
new GDALOpenInfo(osFilename.c_str(), GA_ReadOnly,
|
|
poOpenInfo->GetSiblingFiles());
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Parse header */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int nXSize, nYSize;
|
|
memcpy(&nXSize, poOpenInfo->pabyHeader + 6, 4);
|
|
CPL_LSBPTR32(&nXSize);
|
|
memcpy(&nYSize, poOpenInfo->pabyHeader + 10, 4);
|
|
CPL_LSBPTR32(&nYSize);
|
|
|
|
GUInt16 nTileSize;
|
|
memcpy(&nTileSize, poOpenInfo->pabyHeader + 14, 2);
|
|
CPL_LSBPTR16(&nTileSize);
|
|
|
|
float fVertPres, fHorizScale;
|
|
memcpy(&fVertPres, poOpenInfo->pabyHeader + 16, 4);
|
|
CPL_LSBPTR32(&fVertPres);
|
|
memcpy(&fHorizScale, poOpenInfo->pabyHeader + 20, 4);
|
|
CPL_LSBPTR32(&fHorizScale);
|
|
|
|
GUInt32 nExtendedHeaderLen;
|
|
memcpy(&nExtendedHeaderLen, poOpenInfo->pabyHeader + 24, 4);
|
|
CPL_LSBPTR32(&nExtendedHeaderLen);
|
|
|
|
delete poOpenInfoToDelete;
|
|
poOpenInfoToDelete = NULL;
|
|
|
|
if (nTileSize < 8)
|
|
return NULL;
|
|
if (nXSize <= 0 || nXSize > INT_MAX - nTileSize ||
|
|
nYSize <= 0 || nYSize > INT_MAX - nTileSize)
|
|
return NULL;
|
|
/* To avoid later potential int overflows */
|
|
if (nExtendedHeaderLen > 1024 * 65536)
|
|
return NULL;
|
|
|
|
if (!GDALCheckDatasetDimensions(nXSize, nYSize))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Parse extended blocks */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
VSILFILE* fp = VSIFOpenL(osFilename.c_str(), "rb");
|
|
if (fp == NULL)
|
|
return NULL;
|
|
|
|
VSIFSeekL(fp, 28, SEEK_SET);
|
|
|
|
int bHasExtent = FALSE;
|
|
double dfMinX = 0, dfMaxX = 0, dfMinY = 0, dfMaxY = 0;
|
|
int bHasUTMZone = FALSE;
|
|
GInt16 nUTMZone = 0;
|
|
int bHasEPSGDatumCode = FALSE;
|
|
GInt16 nEPSGDatumCode = 0;
|
|
int bHasEPSGCode = FALSE;
|
|
GInt16 nEPSGCode = 0;
|
|
int bHasRelativePrecision = FALSE;
|
|
float fRelativePrecision = 0;
|
|
char szApplicationName[256];
|
|
szApplicationName[0] = 0;
|
|
|
|
GUInt32 nExtendedHeaderOff = 0;
|
|
while(nExtendedHeaderOff < nExtendedHeaderLen)
|
|
{
|
|
char pabyBlockHeader[24];
|
|
VSIFReadL(pabyBlockHeader, 24, 1, fp);
|
|
|
|
char szBlockName[16 + 1];
|
|
memcpy(szBlockName, pabyBlockHeader + 4, 16);
|
|
szBlockName[16] = 0;
|
|
GUInt32 nBlockSize;
|
|
memcpy(&nBlockSize, pabyBlockHeader + 20, 4);
|
|
CPL_LSBPTR32(&nBlockSize);
|
|
if (nBlockSize > 65536)
|
|
break;
|
|
|
|
nExtendedHeaderOff += 24 + nBlockSize;
|
|
|
|
if (strcmp(szBlockName, "georef-extents") == 0 &&
|
|
nBlockSize == 34)
|
|
{
|
|
char pabyBlockData[34];
|
|
VSIFReadL(pabyBlockData, 34, 1, fp);
|
|
|
|
memcpy(&dfMinX, pabyBlockData + 2, 8);
|
|
CPL_LSBPTR64(&dfMinX);
|
|
memcpy(&dfMaxX, pabyBlockData + 2 + 8, 8);
|
|
CPL_LSBPTR64(&dfMaxX);
|
|
memcpy(&dfMinY, pabyBlockData + 2 + 8 + 8, 8);
|
|
CPL_LSBPTR64(&dfMinY);
|
|
memcpy(&dfMaxY, pabyBlockData + 2 + 8 + 8 + 8, 8);
|
|
CPL_LSBPTR64(&dfMaxY);
|
|
|
|
bHasExtent = TRUE;
|
|
}
|
|
else if (strcmp(szBlockName, "georef-utm") == 0 &&
|
|
nBlockSize == 2)
|
|
{
|
|
VSIFReadL(&nUTMZone, 2, 1, fp);
|
|
CPL_LSBPTR16(&nUTMZone);
|
|
CPLDebug("HF2", "UTM Zone = %d", nUTMZone);
|
|
|
|
bHasUTMZone = TRUE;
|
|
}
|
|
else if (strcmp(szBlockName, "georef-datum") == 0 &&
|
|
nBlockSize == 2)
|
|
{
|
|
VSIFReadL(&nEPSGDatumCode, 2, 1, fp);
|
|
CPL_LSBPTR16(&nEPSGDatumCode);
|
|
CPLDebug("HF2", "EPSG Datum Code = %d", nEPSGDatumCode);
|
|
|
|
bHasEPSGDatumCode = TRUE;
|
|
}
|
|
else if (strcmp(szBlockName, "georef-epsg-prj") == 0 &&
|
|
nBlockSize == 2)
|
|
{
|
|
VSIFReadL(&nEPSGCode, 2, 1, fp);
|
|
CPL_LSBPTR16(&nEPSGCode);
|
|
CPLDebug("HF2", "EPSG Code = %d", nEPSGCode);
|
|
|
|
bHasEPSGCode = TRUE;
|
|
}
|
|
else if (strcmp(szBlockName, "precis-rel") == 0 &&
|
|
nBlockSize == 4)
|
|
{
|
|
VSIFReadL(&fRelativePrecision, 4, 1, fp);
|
|
CPL_LSBPTR32(&fRelativePrecision);
|
|
|
|
bHasRelativePrecision = TRUE;
|
|
}
|
|
else if (strcmp(szBlockName, "app-name") == 0 &&
|
|
nBlockSize < 256)
|
|
{
|
|
VSIFReadL(szApplicationName, nBlockSize, 1, fp);
|
|
szApplicationName[nBlockSize] = 0;
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("HF2", "Skipping block %s", szBlockName);
|
|
VSIFSeekL(fp, nBlockSize, SEEK_CUR);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
HF2Dataset *poDS;
|
|
|
|
poDS = new HF2Dataset();
|
|
poDS->fp = fp;
|
|
poDS->nRasterXSize = nXSize;
|
|
poDS->nRasterYSize = nYSize;
|
|
poDS->nTileSize = nTileSize;
|
|
CPLDebug("HF2", "nXSize = %d, nYSize = %d, nTileSize = %d", nXSize, nYSize, nTileSize);
|
|
if (bHasExtent)
|
|
{
|
|
poDS->adfGeoTransform[0] = dfMinX;
|
|
poDS->adfGeoTransform[3] = dfMaxY;
|
|
poDS->adfGeoTransform[1] = (dfMaxX - dfMinX) / nXSize;
|
|
poDS->adfGeoTransform[5] = -(dfMaxY - dfMinY) / nYSize;
|
|
}
|
|
else
|
|
{
|
|
poDS->adfGeoTransform[1] = fHorizScale;
|
|
poDS->adfGeoTransform[5] = fHorizScale;
|
|
}
|
|
|
|
if (bHasEPSGCode)
|
|
{
|
|
OGRSpatialReference oSRS;
|
|
if (oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
|
|
oSRS.exportToWkt(&poDS->pszWKT);
|
|
}
|
|
else
|
|
{
|
|
int bHasSRS = FALSE;
|
|
OGRSpatialReference oSRS;
|
|
oSRS.SetGeogCS("unknown", "unknown", "unknown", SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING);
|
|
if (bHasEPSGDatumCode)
|
|
{
|
|
if (nEPSGDatumCode == 23 || nEPSGDatumCode == 6326)
|
|
{
|
|
bHasSRS = TRUE;
|
|
oSRS.SetWellKnownGeogCS("WGS84");
|
|
}
|
|
else if (nEPSGDatumCode >= 6000)
|
|
{
|
|
char szName[32];
|
|
sprintf( szName, "EPSG:%d", nEPSGDatumCode-2000 );
|
|
oSRS.SetWellKnownGeogCS( szName );
|
|
bHasSRS = TRUE;
|
|
}
|
|
}
|
|
|
|
if (bHasUTMZone && ABS(nUTMZone) >= 1 && ABS(nUTMZone) <= 60)
|
|
{
|
|
bHasSRS = TRUE;
|
|
oSRS.SetUTM(ABS(nUTMZone), nUTMZone > 0);
|
|
}
|
|
if (bHasSRS)
|
|
oSRS.exportToWkt(&poDS->pszWKT);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->nBands = 1;
|
|
int i;
|
|
for( i = 0; i < poDS->nBands; i++ )
|
|
{
|
|
poDS->SetBand( i+1, new HF2RasterBand( poDS, i+1, GDT_Float32 ) );
|
|
poDS->GetRasterBand(i+1)->SetUnitType("m");
|
|
}
|
|
|
|
if (szApplicationName[0] != '\0')
|
|
poDS->SetMetadataItem("APPLICATION_NAME", szApplicationName);
|
|
poDS->SetMetadataItem("VERTICAL_PRECISION", CPLString().Printf("%f", fVertPres));
|
|
if (bHasRelativePrecision)
|
|
poDS->SetMetadataItem("RELATIVE_VERTICAL_PRECISION", CPLString().Printf("%f", fRelativePrecision));
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( osOriginalFilename.c_str() );
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Support overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->oOvManager.Initialize( poDS, osOriginalFilename.c_str() );
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr HF2Dataset::GetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
memcpy(padfTransform, adfGeoTransform, 6 * sizeof(double));
|
|
|
|
return( CE_None );
|
|
}
|
|
|
|
|
|
static void WriteShort(VSILFILE* fp, GInt16 val)
|
|
{
|
|
CPL_LSBPTR16(&val);
|
|
VSIFWriteL(&val, 2, 1, fp);
|
|
}
|
|
|
|
static void WriteInt(VSILFILE* fp, GInt32 val)
|
|
{
|
|
CPL_LSBPTR32(&val);
|
|
VSIFWriteL(&val, 4, 1, fp);
|
|
}
|
|
|
|
static void WriteFloat(VSILFILE* fp, float val)
|
|
{
|
|
CPL_LSBPTR32(&val);
|
|
VSIFWriteL(&val, 4, 1, fp);
|
|
}
|
|
|
|
static void WriteDouble(VSILFILE* fp, double val)
|
|
{
|
|
CPL_LSBPTR64(&val);
|
|
VSIFWriteL(&val, 8, 1, fp);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CreateCopy() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset* HF2Dataset::CreateCopy( const char * pszFilename,
|
|
GDALDataset *poSrcDS,
|
|
int bStrict, char ** papszOptions,
|
|
GDALProgressFunc pfnProgress,
|
|
void * pProgressData )
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Some some rudimentary checks */
|
|
/* -------------------------------------------------------------------- */
|
|
int nBands = poSrcDS->GetRasterCount();
|
|
if (nBands == 0)
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"HF2 driver does not support source dataset with zero band.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (nBands != 1)
|
|
{
|
|
CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
|
|
"HF2 driver only uses the first band of the dataset.\n");
|
|
if (bStrict)
|
|
return NULL;
|
|
}
|
|
|
|
if( pfnProgress && !pfnProgress( 0.0, NULL, pProgressData ) )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get source dataset info */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int nXSize = poSrcDS->GetRasterXSize();
|
|
int nYSize = poSrcDS->GetRasterYSize();
|
|
double adfGeoTransform[6];
|
|
poSrcDS->GetGeoTransform(adfGeoTransform);
|
|
int bHasGeoTransform = !(adfGeoTransform[0] == 0 &&
|
|
adfGeoTransform[1] == 1 &&
|
|
adfGeoTransform[2] == 0 &&
|
|
adfGeoTransform[3] == 0 &&
|
|
adfGeoTransform[4] == 0 &&
|
|
adfGeoTransform[5] == 1);
|
|
if (adfGeoTransform[2] != 0 || adfGeoTransform[4] != 0)
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"HF2 driver does not support CreateCopy() from skewed or rotated dataset.\n");
|
|
return NULL;
|
|
}
|
|
|
|
GDALDataType eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
|
|
GDALDataType eReqDT;
|
|
float fVertPres = (float) 0.01;
|
|
if (eSrcDT == GDT_Byte || eSrcDT == GDT_Int16)
|
|
{
|
|
fVertPres = 1;
|
|
eReqDT = GDT_Int16;
|
|
}
|
|
else
|
|
eReqDT = GDT_Float32;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read creation options */
|
|
/* -------------------------------------------------------------------- */
|
|
const char* pszCompressed = CSLFetchNameValue(papszOptions, "COMPRESS");
|
|
int bCompress = FALSE;
|
|
if (pszCompressed)
|
|
bCompress = CSLTestBoolean(pszCompressed);
|
|
|
|
const char* pszVerticalPrecision = CSLFetchNameValue(papszOptions, "VERTICAL_PRECISION");
|
|
if (pszVerticalPrecision)
|
|
{
|
|
fVertPres = (float) CPLAtofM(pszVerticalPrecision);
|
|
if (fVertPres <= 0)
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Unsupported value for VERTICAL_PRECISION. Defaulting to 0.01");
|
|
fVertPres = (float) 0.01;
|
|
}
|
|
if (eReqDT == GDT_Int16 && fVertPres > 1)
|
|
eReqDT = GDT_Float32;
|
|
}
|
|
|
|
const char* pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
|
|
int nTileSize = 256;
|
|
if (pszBlockSize)
|
|
{
|
|
nTileSize = atoi(pszBlockSize);
|
|
if (nTileSize < 8 || nTileSize > 4096)
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Unsupported value for BLOCKSIZE. Defaulting to 256");
|
|
nTileSize = 256;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Parse source dataset georeferencing info */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int nExtendedHeaderLen = 0;
|
|
if (bHasGeoTransform)
|
|
nExtendedHeaderLen += 58;
|
|
const char* pszProjectionRef = poSrcDS->GetProjectionRef();
|
|
int nDatumCode = -2;
|
|
int nUTMZone = 0;
|
|
int bNorth = FALSE;
|
|
int nEPSGCode = 0;
|
|
int nExtentUnits = 1;
|
|
if (pszProjectionRef != NULL && pszProjectionRef[0] != '\0')
|
|
{
|
|
OGRSpatialReference oSRS;
|
|
char* pszTemp = (char*) pszProjectionRef;
|
|
if (oSRS.importFromWkt(&pszTemp) == OGRERR_NONE)
|
|
{
|
|
const char* pszValue = NULL;
|
|
if( oSRS.GetAuthorityName( "GEOGCS|DATUM" ) != NULL
|
|
&& EQUAL(oSRS.GetAuthorityName( "GEOGCS|DATUM" ),"EPSG") )
|
|
nDatumCode = atoi(oSRS.GetAuthorityCode( "GEOGCS|DATUM" ));
|
|
else if ((pszValue = oSRS.GetAttrValue("GEOGCS|DATUM")) != NULL)
|
|
{
|
|
if (strstr(pszValue, "WGS") && strstr(pszValue, "84"))
|
|
nDatumCode = 6326;
|
|
}
|
|
|
|
nUTMZone = oSRS.GetUTMZone(&bNorth);
|
|
}
|
|
if( oSRS.GetAuthorityName( "PROJCS" ) != NULL
|
|
&& EQUAL(oSRS.GetAuthorityName( "PROJCS" ),"EPSG") )
|
|
nEPSGCode = atoi(oSRS.GetAuthorityCode( "PROJCS" ));
|
|
|
|
if( oSRS.IsGeographic() )
|
|
{
|
|
nExtentUnits = 0;
|
|
}
|
|
else
|
|
{
|
|
double dfLinear = oSRS.GetLinearUnits();
|
|
|
|
if( ABS(dfLinear - 0.3048) < 0.0000001 )
|
|
nExtentUnits = 2;
|
|
else if( ABS(dfLinear - CPLAtof(SRS_UL_US_FOOT_CONV)) < 0.00000001 )
|
|
nExtentUnits = 3;
|
|
else
|
|
nExtentUnits = 1;
|
|
}
|
|
}
|
|
if (nDatumCode != -2)
|
|
nExtendedHeaderLen += 26;
|
|
if (nUTMZone != 0)
|
|
nExtendedHeaderLen += 26;
|
|
if (nEPSGCode)
|
|
nExtendedHeaderLen += 26;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create target file */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
CPLString osFilename;
|
|
if (bCompress)
|
|
{
|
|
osFilename = "/vsigzip/";
|
|
osFilename += pszFilename;
|
|
}
|
|
else
|
|
osFilename = pszFilename;
|
|
VSILFILE* fp = VSIFOpenL(osFilename.c_str(), "wb");
|
|
if (fp == NULL)
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Cannot create %s", pszFilename );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write header */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
VSIFWriteL("HF2\0", 4, 1, fp);
|
|
WriteShort(fp, 0);
|
|
WriteInt(fp, nXSize);
|
|
WriteInt(fp, nYSize);
|
|
WriteShort(fp, (GInt16) nTileSize);
|
|
WriteFloat(fp, fVertPres);
|
|
float fHorizScale = (float) ((fabs(adfGeoTransform[1]) + fabs(adfGeoTransform[5])) / 2);
|
|
WriteFloat(fp, fHorizScale);
|
|
WriteInt(fp, nExtendedHeaderLen);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write extended header */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
char szBlockName[16 + 1];
|
|
if (bHasGeoTransform)
|
|
{
|
|
VSIFWriteL("bin\0", 4, 1, fp);
|
|
memset(szBlockName, 0, 16 + 1);
|
|
strcpy(szBlockName, "georef-extents");
|
|
VSIFWriteL(szBlockName, 16, 1, fp);
|
|
WriteInt(fp, 34);
|
|
WriteShort(fp, (GInt16) nExtentUnits);
|
|
WriteDouble(fp, adfGeoTransform[0]);
|
|
WriteDouble(fp, adfGeoTransform[0] + nXSize * adfGeoTransform[1]);
|
|
WriteDouble(fp, adfGeoTransform[3] + nYSize * adfGeoTransform[5]);
|
|
WriteDouble(fp, adfGeoTransform[3]);
|
|
}
|
|
if (nUTMZone != 0)
|
|
{
|
|
VSIFWriteL("bin\0", 4, 1, fp);
|
|
memset(szBlockName, 0, 16 + 1);
|
|
strcpy(szBlockName, "georef-utm");
|
|
VSIFWriteL(szBlockName, 16, 1, fp);
|
|
WriteInt(fp, 2);
|
|
WriteShort(fp, (GInt16) ((bNorth) ? nUTMZone : -nUTMZone));
|
|
}
|
|
if (nDatumCode != -2)
|
|
{
|
|
VSIFWriteL("bin\0", 4, 1, fp);
|
|
memset(szBlockName, 0, 16 + 1);
|
|
strcpy(szBlockName, "georef-datum");
|
|
VSIFWriteL(szBlockName, 16, 1, fp);
|
|
WriteInt(fp, 2);
|
|
WriteShort(fp, (GInt16) nDatumCode);
|
|
}
|
|
if (nEPSGCode != 0)
|
|
{
|
|
VSIFWriteL("bin\0", 4, 1, fp);
|
|
memset(szBlockName, 0, 16 + 1);
|
|
strcpy(szBlockName, "georef-epsg-prj");
|
|
VSIFWriteL(szBlockName, 16, 1, fp);
|
|
WriteInt(fp, 2);
|
|
WriteShort(fp, (GInt16) nEPSGCode);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Copy imagery */
|
|
/* -------------------------------------------------------------------- */
|
|
int nXBlocks = (nXSize + nTileSize - 1) / nTileSize;
|
|
int nYBlocks = (nYSize + nTileSize - 1) / nTileSize;
|
|
|
|
void* pTileBuffer = (void*) VSIMalloc(nTileSize * nTileSize * (GDALGetDataTypeSize(eReqDT) / 8));
|
|
if (pTileBuffer == NULL)
|
|
{
|
|
CPLError( CE_Failure, CPLE_OutOfMemory, "Out of memory");
|
|
VSIFCloseL(fp);
|
|
return NULL;
|
|
}
|
|
|
|
int i, j, k, l;
|
|
CPLErr eErr = CE_None;
|
|
for(j=0;j<nYBlocks && eErr == CE_None;j++)
|
|
{
|
|
for(i=0;i<nXBlocks && eErr == CE_None;i++)
|
|
{
|
|
int nReqXSize = MIN(nTileSize, nXSize - i * nTileSize);
|
|
int nReqYSize = MIN(nTileSize, nYSize - j * nTileSize);
|
|
eErr = poSrcDS->GetRasterBand(1)->RasterIO(GF_Read,
|
|
i * nTileSize, MAX(0, nYSize - (j + 1) * nTileSize),
|
|
nReqXSize, nReqYSize,
|
|
pTileBuffer, nReqXSize, nReqYSize,
|
|
eReqDT, 0, 0, NULL);
|
|
if (eErr != CE_None)
|
|
break;
|
|
|
|
if (eReqDT == GDT_Int16)
|
|
{
|
|
WriteFloat(fp, 1); /* scale */
|
|
WriteFloat(fp, 0); /* offset */
|
|
for(k=0;k<nReqYSize;k++)
|
|
{
|
|
int nLastVal = ((short*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + 0];
|
|
GByte nWordSize = 1;
|
|
for(l=1;l<nReqXSize;l++)
|
|
{
|
|
int nVal = ((short*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + l];
|
|
int nDiff = nVal - nLastVal;
|
|
if (nDiff < -32768 || nDiff > 32767)
|
|
{
|
|
nWordSize = 4;
|
|
break;
|
|
}
|
|
if (nDiff < -128 || nDiff > 127)
|
|
nWordSize = 2;
|
|
nLastVal = nVal;
|
|
}
|
|
|
|
VSIFWriteL(&nWordSize, 1, 1, fp);
|
|
nLastVal = ((short*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + 0];
|
|
WriteInt(fp, nLastVal);
|
|
for(l=1;l<nReqXSize;l++)
|
|
{
|
|
int nVal = ((short*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + l];
|
|
int nDiff = nVal - nLastVal;
|
|
if (nWordSize == 1)
|
|
{
|
|
CPLAssert(nDiff >= -128 && nDiff <= 127);
|
|
char chDiff = (char)nDiff;
|
|
VSIFWriteL(&chDiff, 1, 1, fp);
|
|
}
|
|
else if (nWordSize == 2)
|
|
{
|
|
CPLAssert(nDiff >= -32768 && nDiff <= 32767);
|
|
WriteShort(fp, (short)nDiff);
|
|
}
|
|
else
|
|
{
|
|
WriteInt(fp, nDiff);
|
|
}
|
|
nLastVal = nVal;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float fMinVal = ((float*)pTileBuffer)[0];
|
|
float fMaxVal = fMinVal;
|
|
for(k=1;k<nReqYSize*nReqXSize;k++)
|
|
{
|
|
float fVal = ((float*)pTileBuffer)[k];
|
|
if (fVal < fMinVal) fMinVal = fVal;
|
|
if (fVal > fMaxVal) fMaxVal = fVal;
|
|
}
|
|
|
|
float fIntRange = (fMaxVal - fMinVal) / fVertPres;
|
|
float fScale = (fMinVal == fMaxVal) ? 1 : (fMaxVal - fMinVal) / fIntRange;
|
|
float fOffset = fMinVal;
|
|
WriteFloat(fp, fScale); /* scale */
|
|
WriteFloat(fp, fOffset); /* offset */
|
|
for(k=0;k<nReqYSize;k++)
|
|
{
|
|
float fLastVal = ((float*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + 0];
|
|
float fIntLastVal = (fLastVal - fOffset) / fScale;
|
|
CPLAssert(fIntLastVal >= -2147483648.0f && fIntLastVal <= 2147483647.0f);
|
|
int nLastVal = (int)fIntLastVal;
|
|
GByte nWordSize = 1;
|
|
for(l=1;l<nReqXSize;l++)
|
|
{
|
|
float fVal = ((float*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + l];
|
|
float fIntVal = (fVal - fOffset) / fScale;
|
|
CPLAssert(fIntVal >= -2147483648.0f && fIntVal <= 2147483647.0f);
|
|
int nVal = (int)fIntVal;
|
|
int nDiff = nVal - nLastVal;
|
|
CPLAssert((int)((GIntBig)nVal - nLastVal) == nDiff);
|
|
if (nDiff < -32768 || nDiff > 32767)
|
|
{
|
|
nWordSize = 4;
|
|
break;
|
|
}
|
|
if (nDiff < -128 || nDiff > 127)
|
|
nWordSize = 2;
|
|
nLastVal = nVal;
|
|
}
|
|
|
|
VSIFWriteL(&nWordSize, 1, 1, fp);
|
|
fLastVal = ((float*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + 0];
|
|
fIntLastVal = (fLastVal - fOffset) / fScale;
|
|
nLastVal = (int)fIntLastVal;
|
|
WriteInt(fp, nLastVal);
|
|
for(l=1;l<nReqXSize;l++)
|
|
{
|
|
float fVal = ((float*)pTileBuffer)[(nReqYSize - k - 1) * nReqXSize + l];
|
|
float fIntVal = (fVal - fOffset) / fScale;
|
|
int nVal = (int)fIntVal;
|
|
int nDiff = nVal - nLastVal;
|
|
CPLAssert((int)((GIntBig)nVal - nLastVal) == nDiff);
|
|
if (nWordSize == 1)
|
|
{
|
|
CPLAssert(nDiff >= -128 && nDiff <= 127);
|
|
char chDiff = (char)nDiff;
|
|
VSIFWriteL(&chDiff, 1, 1, fp);
|
|
}
|
|
else if (nWordSize == 2)
|
|
{
|
|
CPLAssert(nDiff >= -32768 && nDiff <= 32767);
|
|
WriteShort(fp, (short)nDiff);
|
|
}
|
|
else
|
|
{
|
|
WriteInt(fp, nDiff);
|
|
}
|
|
nLastVal = nVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pfnProgress && !pfnProgress( (j * nXBlocks + i + 1) * 1.0 / (nXBlocks * nYBlocks), NULL, pProgressData ) )
|
|
{
|
|
eErr = CE_Failure;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CPLFree(pTileBuffer);
|
|
|
|
VSIFCloseL(fp);
|
|
|
|
if (eErr != CE_None)
|
|
return NULL;
|
|
|
|
return (GDALDataset*) GDALOpen(osFilename.c_str(), GA_ReadOnly);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_HF2() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_HF2()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if( GDALGetDriverByName( "HF2" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "HF2" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"HF2/HFZ heightfield raster" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_hf2.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "hf2" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
|
|
"<CreationOptionList>"
|
|
" <Option name='VERTICAL_PRECISION' type='float' default='0.01' description='Vertical precision.'/>"
|
|
" <Option name='COMPRESS' type='boolean' default='false' description='Set to true to produce a GZip compressed file.'/>"
|
|
" <Option name='BLOCKSIZE' type='int' default='256' description='Tile size.'/>"
|
|
"</CreationOptionList>");
|
|
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
|
|
poDriver->pfnOpen = HF2Dataset::Open;
|
|
poDriver->pfnIdentify = HF2Dataset::Identify;
|
|
poDriver->pfnCreateCopy = HF2Dataset::CreateCopy;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|
|
|