mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
885 lines
33 KiB
C++
885 lines
33 KiB
C++
/******************************************************************************
|
|
* $Id: ntv2dataset.cpp 27942 2014-11-11 00:57:41Z rouault $
|
|
*
|
|
* Project: Horizontal Datum Formats
|
|
* Purpose: Implementation of NTv2 datum shift format used in Canada, France,
|
|
* Australia and elsewhere.
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
* Financial Support: i-cubed (http://www.i-cubed.com)
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2010, Frank Warmerdam
|
|
* 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 "rawdataset.h"
|
|
#include "cpl_string.h"
|
|
#include "ogr_srs_api.h"
|
|
|
|
CPL_CVSID("$Id: ntv2dataset.cpp 27942 2014-11-11 00:57:41Z rouault $");
|
|
|
|
/**
|
|
* The header for the file, and each grid consists of 11 16byte records.
|
|
* The first half is an ASCII label, and the second half is the value
|
|
* often in a little endian int or float.
|
|
*
|
|
* Example:
|
|
|
|
00000000 4e 55 4d 5f 4f 52 45 43 0b 00 00 00 00 00 00 00 |NUM_OREC........|
|
|
00000010 4e 55 4d 5f 53 52 45 43 0b 00 00 00 00 00 00 00 |NUM_SREC........|
|
|
00000020 4e 55 4d 5f 46 49 4c 45 01 00 00 00 00 00 00 00 |NUM_FILE........|
|
|
00000030 47 53 5f 54 59 50 45 20 53 45 43 4f 4e 44 53 20 |GS_TYPE SECONDS |
|
|
00000040 56 45 52 53 49 4f 4e 20 49 47 4e 30 37 5f 30 31 |VERSION IGN07_01|
|
|
00000050 53 59 53 54 45 4d 5f 46 4e 54 46 20 20 20 20 20 |SYSTEM_FNTF |
|
|
00000060 53 59 53 54 45 4d 5f 54 52 47 46 39 33 20 20 20 |SYSTEM_TRGF93 |
|
|
00000070 4d 41 4a 4f 52 5f 46 20 cd cc cc 4c c2 54 58 41 |MAJOR_F ...L.TXA|
|
|
00000080 4d 49 4e 4f 52 5f 46 20 00 00 00 c0 88 3f 58 41 |MINOR_F .....?XA|
|
|
00000090 4d 41 4a 4f 52 5f 54 20 00 00 00 40 a6 54 58 41 |MAJOR_T ...@.TXA|
|
|
000000a0 4d 49 4e 4f 52 5f 54 20 27 e0 1a 14 c4 3f 58 41 |MINOR_T '....?XA|
|
|
000000b0 53 55 42 5f 4e 41 4d 45 46 52 41 4e 43 45 20 20 |SUB_NAMEFRANCE |
|
|
000000c0 50 41 52 45 4e 54 20 20 4e 4f 4e 45 20 20 20 20 |PARENT NONE |
|
|
000000d0 43 52 45 41 54 45 44 20 33 31 2f 31 30 2f 30 37 |CREATED 31/10/07|
|
|
000000e0 55 50 44 41 54 45 44 20 20 20 20 20 20 20 20 20 |UPDATED |
|
|
000000f0 53 5f 4c 41 54 20 20 20 00 00 00 00 80 04 02 41 |S_LAT .......A|
|
|
00000100 4e 5f 4c 41 54 20 20 20 00 00 00 00 00 da 06 41 |N_LAT .......A|
|
|
00000110 45 5f 4c 4f 4e 47 20 20 00 00 00 00 00 94 e1 c0 |E_LONG ........|
|
|
00000120 57 5f 4c 4f 4e 47 20 20 00 00 00 00 00 56 d3 40 |W_LONG .....V.@|
|
|
00000130 4c 41 54 5f 49 4e 43 20 00 00 00 00 00 80 76 40 |LAT_INC ......v@|
|
|
00000140 4c 4f 4e 47 5f 49 4e 43 00 00 00 00 00 80 76 40 |LONG_INC......v@|
|
|
00000150 47 53 5f 43 4f 55 4e 54 a4 43 00 00 00 00 00 00 |GS_COUNT.C......|
|
|
00000160 94 f7 c1 3e 70 ee a3 3f 2a c7 84 3d ff 42 af 3d |...>p..?*..=.B.=|
|
|
|
|
the actual grid data is a raster with 4 float32 bands (lat offset, long
|
|
offset, lat error, long error). The offset values are in arc seconds.
|
|
The grid is flipped in the x and y axis from our usual GDAL orientation.
|
|
That is, the first pixel is the south east corner with scanlines going
|
|
east to west, and rows from south to north. As a GDAL dataset we represent
|
|
these both in the more conventional orientation.
|
|
*/
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* NTv2Dataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class NTv2Dataset : public RawDataset
|
|
{
|
|
public:
|
|
VSILFILE *fpImage; // image data file.
|
|
|
|
int nRecordLength;
|
|
vsi_l_offset nGridOffset;
|
|
|
|
double adfGeoTransform[6];
|
|
|
|
void CaptureMetadataItem( char *pszItem );
|
|
|
|
int OpenGrid( char *pachGridHeader, vsi_l_offset nDataStart );
|
|
|
|
public:
|
|
NTv2Dataset();
|
|
~NTv2Dataset();
|
|
|
|
virtual CPLErr SetGeoTransform( double * padfTransform );
|
|
virtual CPLErr GetGeoTransform( double * padfTransform );
|
|
virtual const char *GetProjectionRef();
|
|
virtual void FlushCache(void);
|
|
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
static int Identify( GDALOpenInfo * );
|
|
static GDALDataset *Create( const char * pszFilename,
|
|
int nXSize, int nYSize, int nBands,
|
|
GDALDataType eType, char ** papszOptions );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* NTv2Dataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
/************************************************************************/
|
|
/* NTv2Dataset() */
|
|
/************************************************************************/
|
|
|
|
NTv2Dataset::NTv2Dataset()
|
|
{
|
|
fpImage = NULL;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~NTv2Dataset() */
|
|
/************************************************************************/
|
|
|
|
NTv2Dataset::~NTv2Dataset()
|
|
|
|
{
|
|
FlushCache();
|
|
|
|
if( fpImage != NULL )
|
|
VSIFCloseL( fpImage );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FlushCache() */
|
|
/************************************************************************/
|
|
|
|
void NTv2Dataset::FlushCache()
|
|
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Nothing to do in readonly mode, or if nothing seems to have */
|
|
/* changed metadata wise. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( eAccess != GA_Update || !(GetPamFlags() & GPF_DIRTY) )
|
|
{
|
|
RawDataset::FlushCache();
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Load grid and file headers. */
|
|
/* -------------------------------------------------------------------- */
|
|
char achFileHeader[11*16];
|
|
char achGridHeader[11*16];
|
|
|
|
VSIFSeekL( fpImage, 0, SEEK_SET );
|
|
VSIFReadL( achFileHeader, 11, 16, fpImage );
|
|
|
|
VSIFSeekL( fpImage, nGridOffset, SEEK_SET );
|
|
VSIFReadL( achGridHeader, 11, 16, fpImage );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Update the grid, and file headers with any available */
|
|
/* metadata. If all available metadata is recognised then mark */
|
|
/* things "clean" from a PAM point of view. */
|
|
/* -------------------------------------------------------------------- */
|
|
char **papszMD = GetMetadata();
|
|
int i;
|
|
int bSomeLeftOver = FALSE;
|
|
|
|
for( i = 0; papszMD != NULL && papszMD[i] != NULL; i++ )
|
|
{
|
|
char *pszKey = NULL;
|
|
const char *pszValue = CPLParseNameValue( papszMD[i], &pszKey );
|
|
if( pszKey == NULL )
|
|
continue;
|
|
|
|
if( EQUAL(pszKey,"GS_TYPE") )
|
|
{
|
|
memcpy( achFileHeader + 3*16+8, " ", 8 );
|
|
memcpy( achFileHeader + 3*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"VERSION") )
|
|
{
|
|
memcpy( achFileHeader + 4*16+8, " ", 8 );
|
|
memcpy( achFileHeader + 4*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"SYSTEM_F") )
|
|
{
|
|
memcpy( achFileHeader + 5*16+8, " ", 8 );
|
|
memcpy( achFileHeader + 5*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"SYSTEM_T") )
|
|
{
|
|
memcpy( achFileHeader + 6*16+8, " ", 8 );
|
|
memcpy( achFileHeader + 6*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"MAJOR_F") )
|
|
{
|
|
double dfValue = CPLAtof(pszValue);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achFileHeader + 7*16+8, &dfValue, 8 );
|
|
}
|
|
else if( EQUAL(pszKey,"MINOR_F") )
|
|
{
|
|
double dfValue = CPLAtof(pszValue);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achFileHeader + 8*16+8, &dfValue, 8 );
|
|
}
|
|
else if( EQUAL(pszKey,"MAJOR_T") )
|
|
{
|
|
double dfValue = CPLAtof(pszValue);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achFileHeader + 9*16+8, &dfValue, 8 );
|
|
}
|
|
else if( EQUAL(pszKey,"MINOR_T") )
|
|
{
|
|
double dfValue = CPLAtof(pszValue);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achFileHeader + 10*16+8, &dfValue, 8 );
|
|
}
|
|
else if( EQUAL(pszKey,"SUB_NAME") )
|
|
{
|
|
memcpy( achGridHeader + 0*16+8, " ", 8 );
|
|
memcpy( achGridHeader + 0*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"PARENT") )
|
|
{
|
|
memcpy( achGridHeader + 1*16+8, " ", 8 );
|
|
memcpy( achGridHeader + 1*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"CREATED") )
|
|
{
|
|
memcpy( achGridHeader + 2*16+8, " ", 8 );
|
|
memcpy( achGridHeader + 2*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else if( EQUAL(pszKey,"UPDATED") )
|
|
{
|
|
memcpy( achGridHeader + 3*16+8, " ", 8 );
|
|
memcpy( achGridHeader + 3*16+8, pszValue, MIN(8,strlen(pszValue)) );
|
|
}
|
|
else
|
|
{
|
|
bSomeLeftOver = TRUE;
|
|
}
|
|
|
|
CPLFree( pszKey );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Load grid and file headers. */
|
|
/* -------------------------------------------------------------------- */
|
|
VSIFSeekL( fpImage, 0, SEEK_SET );
|
|
VSIFWriteL( achFileHeader, 11, 16, fpImage );
|
|
|
|
VSIFSeekL( fpImage, nGridOffset, SEEK_SET );
|
|
VSIFWriteL( achGridHeader, 11, 16, fpImage );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Clear flags if we got everything, then let pam and below do */
|
|
/* their flushing. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( !bSomeLeftOver )
|
|
SetPamFlags( GetPamFlags() & (~GPF_DIRTY) );
|
|
|
|
RawDataset::FlushCache();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int NTv2Dataset::Identify( GDALOpenInfo *poOpenInfo )
|
|
|
|
{
|
|
if( EQUALN(poOpenInfo->pszFilename,"NTv2:",5) )
|
|
return TRUE;
|
|
|
|
if( poOpenInfo->nHeaderBytes < 64 )
|
|
return FALSE;
|
|
|
|
if( !EQUALN((const char *)poOpenInfo->pabyHeader + 0, "NUM_OREC", 8 ) )
|
|
return FALSE;
|
|
|
|
if( !EQUALN((const char *)poOpenInfo->pabyHeader +16, "NUM_SREC", 8 ) )
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *NTv2Dataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if( !Identify( poOpenInfo ) )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Are we targetting a particular grid? */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString osFilename;
|
|
int iTargetGrid = -1;
|
|
|
|
if( EQUALN(poOpenInfo->pszFilename,"NTv2:",5) )
|
|
{
|
|
const char *pszRest = poOpenInfo->pszFilename+5;
|
|
|
|
iTargetGrid = atoi(pszRest);
|
|
while( *pszRest != '\0' && *pszRest != ':' )
|
|
pszRest++;
|
|
|
|
if( *pszRest == ':' )
|
|
pszRest++;
|
|
|
|
osFilename = pszRest;
|
|
}
|
|
else
|
|
osFilename = poOpenInfo->pszFilename;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
NTv2Dataset *poDS;
|
|
|
|
poDS = new NTv2Dataset();
|
|
poDS->eAccess = poOpenInfo->eAccess;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Open the file. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poOpenInfo->eAccess == GA_ReadOnly )
|
|
poDS->fpImage = VSIFOpenL( osFilename, "rb" );
|
|
else
|
|
poDS->fpImage = VSIFOpenL( osFilename, "rb+" );
|
|
|
|
if( poDS->fpImage == NULL )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the file header. */
|
|
/* -------------------------------------------------------------------- */
|
|
char achHeader[11*16];
|
|
GInt32 nSubFileCount;
|
|
double dfValue;
|
|
CPLString osFValue;
|
|
|
|
VSIFSeekL( poDS->fpImage, 0, SEEK_SET );
|
|
VSIFReadL( achHeader, 11, 16, poDS->fpImage );
|
|
|
|
CPL_LSBPTR32( achHeader + 2*16 + 8 );
|
|
memcpy( &nSubFileCount, achHeader + 2*16 + 8, 4 );
|
|
if (nSubFileCount <= 0 || nSubFileCount >= 1024)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid value for NUM_FILE : %d", nSubFileCount);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
poDS->CaptureMetadataItem( achHeader + 3*16 );
|
|
poDS->CaptureMetadataItem( achHeader + 4*16 );
|
|
poDS->CaptureMetadataItem( achHeader + 5*16 );
|
|
poDS->CaptureMetadataItem( achHeader + 6*16 );
|
|
|
|
memcpy( &dfValue, achHeader + 7*16 + 8, 8 );
|
|
CPL_LSBPTR64( &dfValue );
|
|
osFValue.Printf( "%.15g", dfValue );
|
|
poDS->SetMetadataItem( "MAJOR_F", osFValue );
|
|
|
|
memcpy( &dfValue, achHeader + 8*16 + 8, 8 );
|
|
CPL_LSBPTR64( &dfValue );
|
|
osFValue.Printf( "%.15g", dfValue );
|
|
poDS->SetMetadataItem( "MINOR_F", osFValue );
|
|
|
|
memcpy( &dfValue, achHeader + 9*16 + 8, 8 );
|
|
CPL_LSBPTR64( &dfValue );
|
|
osFValue.Printf( "%.15g", dfValue );
|
|
poDS->SetMetadataItem( "MAJOR_T", osFValue );
|
|
|
|
memcpy( &dfValue, achHeader + 10*16 + 8, 8 );
|
|
CPL_LSBPTR64( &dfValue );
|
|
osFValue.Printf( "%.15g", dfValue );
|
|
poDS->SetMetadataItem( "MINOR_T", osFValue );
|
|
|
|
/* ==================================================================== */
|
|
/* Loop over grids. */
|
|
/* ==================================================================== */
|
|
int iGrid;
|
|
vsi_l_offset nGridOffset = sizeof(achHeader);
|
|
|
|
for( iGrid = 0; iGrid < nSubFileCount; iGrid++ )
|
|
{
|
|
CPLString osSubName;
|
|
int i;
|
|
GUInt32 nGSCount;
|
|
|
|
VSIFSeekL( poDS->fpImage, nGridOffset, SEEK_SET );
|
|
if (VSIFReadL( achHeader, 11, 16, poDS->fpImage ) != 16)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot read header for subfile %d", iGrid);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
for( i = 4; i <= 9; i++ )
|
|
CPL_LSBPTR64( achHeader + i*16 + 8 );
|
|
|
|
CPL_LSBPTR32( achHeader + 10*16 + 8 );
|
|
|
|
memcpy( &nGSCount, achHeader + 10*16 + 8, 4 );
|
|
|
|
osSubName.assign( achHeader + 8, 8 );
|
|
osSubName.Trim();
|
|
|
|
// If this is our target grid, open it as a dataset.
|
|
if( iTargetGrid == iGrid || (iTargetGrid == -1 && iGrid == 0) )
|
|
{
|
|
if( !poDS->OpenGrid( achHeader, nGridOffset ) )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// If we are opening the file as a whole, list subdatasets.
|
|
if( iTargetGrid == -1 )
|
|
{
|
|
CPLString osKey, osValue;
|
|
|
|
osKey.Printf( "SUBDATASET_%d_NAME", iGrid );
|
|
osValue.Printf( "NTv2:%d:%s", iGrid, osFilename.c_str() );
|
|
poDS->SetMetadataItem( osKey, osValue, "SUBDATASETS" );
|
|
|
|
osKey.Printf( "SUBDATASET_%d_DESC", iGrid );
|
|
osValue.Printf( "%s", osSubName.c_str() );
|
|
poDS->SetMetadataItem( osKey, osValue, "SUBDATASETS" );
|
|
}
|
|
|
|
nGridOffset += (11+(vsi_l_offset)nGSCount) * 16;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OpenGrid() */
|
|
/* */
|
|
/* Note that the caller will already have byte swapped needed */
|
|
/* portions of the header. */
|
|
/************************************************************************/
|
|
|
|
int NTv2Dataset::OpenGrid( char *pachHeader, vsi_l_offset nGridOffset )
|
|
|
|
{
|
|
this->nGridOffset = nGridOffset;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the grid header. */
|
|
/* -------------------------------------------------------------------- */
|
|
double s_lat, n_lat, e_long, w_long, lat_inc, long_inc;
|
|
|
|
CaptureMetadataItem( pachHeader + 0*16 );
|
|
CaptureMetadataItem( pachHeader + 1*16 );
|
|
CaptureMetadataItem( pachHeader + 2*16 );
|
|
CaptureMetadataItem( pachHeader + 3*16 );
|
|
|
|
memcpy( &s_lat, pachHeader + 4*16 + 8, 8 );
|
|
memcpy( &n_lat, pachHeader + 5*16 + 8, 8 );
|
|
memcpy( &e_long, pachHeader + 6*16 + 8, 8 );
|
|
memcpy( &w_long, pachHeader + 7*16 + 8, 8 );
|
|
memcpy( &lat_inc, pachHeader + 8*16 + 8, 8 );
|
|
memcpy( &long_inc, pachHeader + 9*16 + 8, 8 );
|
|
|
|
e_long *= -1;
|
|
w_long *= -1;
|
|
|
|
nRasterXSize = (int) floor((e_long - w_long) / long_inc + 1.5);
|
|
nRasterYSize = (int) floor((n_lat - s_lat) / lat_inc + 1.5);
|
|
|
|
if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
|
|
return FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information object. */
|
|
/* */
|
|
/* We use unusual offsets to remap from bottom to top, to top */
|
|
/* to bottom orientation, and also to remap east to west, to */
|
|
/* west to east. */
|
|
/* -------------------------------------------------------------------- */
|
|
int iBand;
|
|
|
|
for( iBand = 0; iBand < 4; iBand++ )
|
|
{
|
|
RawRasterBand *poBand =
|
|
new RawRasterBand( this, iBand+1, fpImage,
|
|
nGridOffset + 4*iBand + 11*16
|
|
+ (nRasterXSize-1) * 16
|
|
+ (nRasterYSize-1) * 16 * nRasterXSize,
|
|
-16, -16 * nRasterXSize,
|
|
GDT_Float32, CPL_IS_LSB, TRUE, FALSE );
|
|
SetBand( iBand+1, poBand );
|
|
}
|
|
|
|
GetRasterBand(1)->SetDescription( "Latitude Offset (arc seconds)" );
|
|
GetRasterBand(2)->SetDescription( "Longitude Offset (arc seconds)" );
|
|
GetRasterBand(3)->SetDescription( "Latitude Error" );
|
|
GetRasterBand(4)->SetDescription( "Longitude Error" );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Setup georeferencing. */
|
|
/* -------------------------------------------------------------------- */
|
|
adfGeoTransform[0] = (w_long - long_inc*0.5) / 3600.0;
|
|
adfGeoTransform[1] = long_inc / 3600.0;
|
|
adfGeoTransform[2] = 0.0;
|
|
adfGeoTransform[3] = (n_lat + lat_inc*0.5) / 3600.0;
|
|
adfGeoTransform[4] = 0.0;
|
|
adfGeoTransform[5] = (-1 * lat_inc) / 3600.0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CaptureMetadataItem() */
|
|
/************************************************************************/
|
|
|
|
void NTv2Dataset::CaptureMetadataItem( char *pszItem )
|
|
|
|
{
|
|
CPLString osKey, osValue;
|
|
|
|
osKey.assign( pszItem, 8 );
|
|
osValue.assign( pszItem+8, 8 );
|
|
|
|
SetMetadataItem( osKey.Trim(), osValue.Trim() );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr NTv2Dataset::GetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr NTv2Dataset::SetGeoTransform( double * padfTransform )
|
|
|
|
{
|
|
if( eAccess == GA_ReadOnly )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NoWriteAccess,
|
|
"Unable to update geotransform on readonly file." );
|
|
return CE_Failure;
|
|
}
|
|
|
|
if( padfTransform[2] != 0.0 || padfTransform[4] != 0.0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Rotated and sheared geotransforms not supported for NTv2.");
|
|
return CE_Failure;
|
|
}
|
|
|
|
memcpy( adfGeoTransform, padfTransform, sizeof(double)*6 );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Update grid header. */
|
|
/* -------------------------------------------------------------------- */
|
|
double dfValue;
|
|
char achHeader[11*16];
|
|
|
|
// read grid header
|
|
VSIFSeekL( fpImage, nGridOffset, SEEK_SET );
|
|
VSIFReadL( achHeader, 11, 16, fpImage );
|
|
|
|
// S_LAT
|
|
dfValue = 3600 * (adfGeoTransform[3] + (nRasterYSize-0.5) * adfGeoTransform[5]);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 4*16 + 8, &dfValue, 8 );
|
|
|
|
// N_LAT
|
|
dfValue = 3600 * (adfGeoTransform[3] + 0.5 * adfGeoTransform[5]);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 5*16 + 8, &dfValue, 8 );
|
|
|
|
// E_LONG
|
|
dfValue = -3600 * (adfGeoTransform[0] + (nRasterXSize-0.5)*adfGeoTransform[1]);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 6*16 + 8, &dfValue, 8 );
|
|
|
|
// W_LONG
|
|
dfValue = -3600 * (adfGeoTransform[0] + 0.5 * adfGeoTransform[1]);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 7*16 + 8, &dfValue, 8 );
|
|
|
|
// LAT_INC
|
|
dfValue = -3600 * adfGeoTransform[5];
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 8*16 + 8, &dfValue, 8 );
|
|
|
|
// LONG_INC
|
|
dfValue = 3600 * adfGeoTransform[1];
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 9*16 + 8, &dfValue, 8 );
|
|
|
|
// write grid header.
|
|
VSIFSeekL( fpImage, nGridOffset, SEEK_SET );
|
|
VSIFWriteL( achHeader, 11, 16, fpImage );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* GetProjectionRef() */
|
|
/************************************************************************/
|
|
|
|
const char *NTv2Dataset::GetProjectionRef()
|
|
|
|
{
|
|
return SRS_WKT_WGS84;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Create() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *NTv2Dataset::Create( const char * pszFilename,
|
|
int nXSize, int nYSize,
|
|
CPL_UNUSED int nBands,
|
|
GDALDataType eType,
|
|
char ** papszOptions )
|
|
{
|
|
if( eType != GDT_Float32 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Attempt to create NTv2 file with unsupported data type '%s'.",
|
|
GDALGetDataTypeName( eType ) );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Are we extending an existing file? */
|
|
/* -------------------------------------------------------------------- */
|
|
VSILFILE *fp;
|
|
GUInt32 nNumFile = 1;
|
|
|
|
int bAppend = CSLFetchBoolean(papszOptions,"APPEND_SUBDATASET",FALSE);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Try to open or create file. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( bAppend )
|
|
fp = VSIFOpenL( pszFilename, "rb+" );
|
|
else
|
|
fp = VSIFOpenL( pszFilename, "wb" );
|
|
|
|
if( fp == NULL )
|
|
{
|
|
CPLError( CE_Failure, CPLE_OpenFailed,
|
|
"Attempt to open/create file `%s' failed.\n",
|
|
pszFilename );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a file level header if we are creating new. */
|
|
/* -------------------------------------------------------------------- */
|
|
char achHeader[11*16];
|
|
const char *pszValue;
|
|
|
|
if( !bAppend )
|
|
{
|
|
memset( achHeader, 0, sizeof(achHeader) );
|
|
|
|
memcpy( achHeader + 0*16, "NUM_OREC", 8 );
|
|
achHeader[ 0*16 + 8] = 0xb;
|
|
|
|
memcpy( achHeader + 1*16, "NUM_SREC", 8 );
|
|
achHeader[ 1*16 + 8] = 0xb;
|
|
|
|
memcpy( achHeader + 2*16, "NUM_FILE", 8 );
|
|
achHeader[ 2*16 + 8] = 0x1;
|
|
|
|
memcpy( achHeader + 3*16, "GS_TYPE ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "GS_TYPE", "SECONDS");
|
|
memcpy( achHeader + 3*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 4*16, "VERSION ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "VERSION", "" );
|
|
memcpy( achHeader + 4*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 5*16, "SYSTEM_F ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "SYSTEM_F", "" );
|
|
memcpy( achHeader + 5*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 6*16, "SYSTEM_T ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "SYSTEM_T", "" );
|
|
memcpy( achHeader + 6*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 7*16, "MAJOR_F ", 8);
|
|
memcpy( achHeader + 8*16, "MINOR_F ", 8 );
|
|
memcpy( achHeader + 9*16, "MAJOR_T ", 8 );
|
|
memcpy( achHeader + 10*16, "MINOR_T ", 8 );
|
|
|
|
VSIFWriteL( achHeader, 1, sizeof(achHeader), fp );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Otherwise update the header with an increased subfile count, */
|
|
/* and advanced to the last record of the file. */
|
|
/* -------------------------------------------------------------------- */
|
|
else
|
|
{
|
|
VSIFSeekL( fp, 2*16 + 8, SEEK_SET );
|
|
VSIFReadL( &nNumFile, 1, 4, fp );
|
|
CPL_LSBPTR32( &nNumFile );
|
|
|
|
nNumFile++;
|
|
|
|
CPL_LSBPTR32( &nNumFile );
|
|
VSIFSeekL( fp, 2*16 + 8, SEEK_SET );
|
|
VSIFWriteL( &nNumFile, 1, 4, fp );
|
|
|
|
vsi_l_offset nEnd;
|
|
|
|
VSIFSeekL( fp, 0, SEEK_END );
|
|
nEnd = VSIFTellL( fp );
|
|
VSIFSeekL( fp, nEnd-16, SEEK_SET );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write the grid header. */
|
|
/* -------------------------------------------------------------------- */
|
|
memset( achHeader, 0, sizeof(achHeader) );
|
|
|
|
memcpy( achHeader + 0*16, "SUB_NAME ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "SUB_NAME", "" );
|
|
memcpy( achHeader + 0*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 1*16, "PARENT ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "PARENT", "NONE" );
|
|
memcpy( achHeader + 1*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 2*16, "CREATED ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "CREATED", "" );
|
|
memcpy( achHeader + 2*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
memcpy( achHeader + 3*16, "UPDATED ", 16 );
|
|
pszValue = CSLFetchNameValueDef( papszOptions, "UPDATED", "" );
|
|
memcpy( achHeader + 3*16+8, pszValue, MIN(16,strlen(pszValue)) );
|
|
|
|
double dfValue;
|
|
|
|
memcpy( achHeader + 4*16, "S_LAT ", 8 );
|
|
dfValue = 0;
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 4*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 5*16, "N_LAT ", 8 );
|
|
dfValue = nYSize-1;
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 5*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 6*16, "E_LONG ", 8 );
|
|
dfValue = -1*(nXSize-1);
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 6*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 7*16, "W_LONG ", 8 );
|
|
dfValue = 0;
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 7*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 8*16, "LAT_INC ", 8 );
|
|
dfValue = 1;
|
|
CPL_LSBPTR64( &dfValue );
|
|
memcpy( achHeader + 8*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 9*16, "LONG_INC", 8 );
|
|
memcpy( achHeader + 9*16 + 8, &dfValue, 8 );
|
|
|
|
memcpy( achHeader + 10*16, "GS_COUNT", 8 );
|
|
GUInt32 nGSCount = nXSize * nYSize;
|
|
CPL_LSBPTR32( &nGSCount );
|
|
memcpy( achHeader + 10*16+8, &nGSCount, 4 );
|
|
|
|
VSIFWriteL( achHeader, 1, sizeof(achHeader), fp );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write zeroed grid data. */
|
|
/* -------------------------------------------------------------------- */
|
|
int i;
|
|
|
|
memset( achHeader, 0, 16 );
|
|
|
|
// Use -1 (0x000080bf) as the default error value.
|
|
memset( achHeader + 10, 0x80, 1 );
|
|
memset( achHeader + 11, 0xbf, 1 );
|
|
memset( achHeader + 14, 0x80, 1 );
|
|
memset( achHeader + 15, 0xbf, 1 );
|
|
|
|
for( i = 0; i < nXSize * nYSize; i++ )
|
|
VSIFWriteL( achHeader, 1, 16, fp );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write the end record. */
|
|
/* -------------------------------------------------------------------- */
|
|
memset( achHeader, 0, 16 );
|
|
memcpy( achHeader, "END ", 8 );
|
|
VSIFWriteL( achHeader, 1, 16, fp );
|
|
VSIFCloseL( fp );
|
|
|
|
if( nNumFile == 1 )
|
|
return (GDALDataset *) GDALOpen( pszFilename, GA_Update );
|
|
else
|
|
{
|
|
CPLString osSubDSName;
|
|
osSubDSName.Printf( "NTv2:%d:%s", nNumFile-1, pszFilename );
|
|
return (GDALDataset *) GDALOpen( osSubDSName, GA_Update );
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_NTv2() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_NTv2()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if( GDALGetDriverByName( "NTv2" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "NTv2" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"NTv2 Datum Grid Shift" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gsb" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
|
|
"Float32" );
|
|
|
|
poDriver->pfnOpen = NTv2Dataset::Open;
|
|
poDriver->pfnIdentify = NTv2Dataset::Identify;
|
|
poDriver->pfnCreate = NTv2Dataset::Create;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|