ultimatepp/bazaar/plugin/gdal/frmts/leveller/levellerdataset.cpp
cxl 23ff1e7e82 .gdal moved to bazaar
git-svn-id: svn://ultimatepp.org/upp/trunk@9273 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2015-12-07 13:36:24 +00:00

1623 lines
45 KiB
C++

/******************************************************************************
* levellerdataset.cpp,v 1.22
*
* Project: Leveller TER Driver
* Purpose: Reader for Leveller TER documents
* Author: Ray Gardener, Daylon Graphics Ltd.
*
* Portions of this module derived from GDAL drivers by
* Frank Warmerdam, see http://www.gdal.org
*
******************************************************************************
* Copyright (c) 2005-2007 Daylon Graphics Ltd.
* Copyright (c) 2007-2013, Even Rouault <even dot rouault at mines-paris dot org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "gdal_pam.h"
#include "ogr_spatialref.h"
CPL_CVSID("$Id: levellerdataset.cpp 28435 2015-02-07 14:35:34Z rouault $");
CPL_C_START
void GDALRegister_Leveller(void);
CPL_C_END
#if 1
#define str_equal(_s1, _s2) (0 == strcmp((_s1),(_s2)))
#define array_size(_a) (sizeof(_a) / sizeof(_a[0]))
/*GDALDataset *LevellerCreateCopy( const char *, GDALDataset *, int, char **,
GDALProgressFunc pfnProgress,
void * pProgressData );
*/
/************************************************************************/
/* ==================================================================== */
/* LevellerDataset */
/* ==================================================================== */
/************************************************************************/
static const size_t kMaxTagNameLen = 63;
enum
{
// Leveller coordsys types.
LEV_COORDSYS_RASTER = 0,
LEV_COORDSYS_LOCAL,
LEV_COORDSYS_GEO
};
enum
{
// Leveller digital axis extent styles.
LEV_DA_POSITIONED = 0,
LEV_DA_SIZED,
LEV_DA_PIXEL_SIZED
};
typedef enum
{
// Measurement unit IDs, OEM version.
UNITLABEL_UNKNOWN = 0x00000000,
UNITLABEL_PIXEL = 0x70780000,
UNITLABEL_PERCENT = 0x25000000,
UNITLABEL_RADIAN = 0x72616400,
UNITLABEL_DEGREE = 0x64656700,
UNITLABEL_ARCMINUTE = 0x6172636D,
UNITLABEL_ARCSECOND = 0x61726373,
UNITLABEL_YM = 0x796D0000,
UNITLABEL_ZM = 0x7A6D0000,
UNITLABEL_AM = 0x616D0000,
UNITLABEL_FM = 0x666D0000,
UNITLABEL_PM = 0x706D0000,
UNITLABEL_A = 0x41000000,
UNITLABEL_NM = 0x6E6D0000,
UNITLABEL_U = 0x75000000,
UNITLABEL_UM = 0x756D0000,
UNITLABEL_PPT = 0x70707400,
UNITLABEL_PT = 0x70740000,
UNITLABEL_MM = 0x6D6D0000,
UNITLABEL_P = 0x70000000,
UNITLABEL_CM = 0x636D0000,
UNITLABEL_IN = 0x696E0000,
UNITLABEL_DFT = 0x64667400,
UNITLABEL_DM = 0x646D0000,
UNITLABEL_LI = 0x6C690000,
UNITLABEL_SLI = 0x736C6900,
UNITLABEL_SP = 0x73700000,
UNITLABEL_FT = 0x66740000,
UNITLABEL_SFT = 0x73667400,
UNITLABEL_YD = 0x79640000,
UNITLABEL_SYD = 0x73796400,
UNITLABEL_M = 0x6D000000,
UNITLABEL_FATH = 0x66617468,
UNITLABEL_R = 0x72000000,
UNITLABEL_RD = UNITLABEL_R,
UNITLABEL_DAM = 0x64416D00,
UNITLABEL_DKM = UNITLABEL_DAM,
UNITLABEL_CH = 0x63680000,
UNITLABEL_SCH = 0x73636800,
UNITLABEL_HM = 0x686D0000,
UNITLABEL_F = 0x66000000,
UNITLABEL_KM = 0x6B6D0000,
UNITLABEL_MI = 0x6D690000,
UNITLABEL_SMI = 0x736D6900,
UNITLABEL_NMI = 0x6E6D6900,
UNITLABEL_MEGAM = 0x4D6D0000,
UNITLABEL_LS = 0x6C730000,
UNITLABEL_GM = 0x476D0000,
UNITLABEL_LM = 0x6C6D0000,
UNITLABEL_AU = 0x41550000,
UNITLABEL_TM = 0x546D0000,
UNITLABEL_LHR = 0x6C687200,
UNITLABEL_LD = 0x6C640000,
UNITLABEL_PETAM = 0x506D0000,
UNITLABEL_LY = 0x6C790000,
UNITLABEL_PC = 0x70630000,
UNITLABEL_EXAM = 0x456D0000,
UNITLABEL_KLY = 0x6B6C7900,
UNITLABEL_KPC = 0x6B706300,
UNITLABEL_ZETTAM = 0x5A6D0000,
UNITLABEL_MLY = 0x4D6C7900,
UNITLABEL_MPC = 0x4D706300,
UNITLABEL_YOTTAM = 0x596D0000
} UNITLABEL;
typedef struct
{
const char* pszID;
double dScale;
UNITLABEL oemCode;
} measurement_unit;
static const double kdays_per_year = 365.25;
static const double kdLStoM = 299792458.0;
static const double kdLYtoM = kdLStoM * kdays_per_year * 24 * 60 * 60;
static const double kdInch = 0.3048 / 12;
static const double kPI = 3.1415926535897932384626433832795;
static const int kFirstLinearMeasureIdx = 9;
static const measurement_unit kUnits[] =
{
{ "", 1.0, UNITLABEL_UNKNOWN },
{ "px", 1.0, UNITLABEL_PIXEL },
{ "%", 1.0, UNITLABEL_PERCENT }, // not actually used
{ "rad", 1.0, UNITLABEL_RADIAN },
{ "\xB0", kPI / 180.0, UNITLABEL_DEGREE }, // \xB0 is Unicode degree symbol
{ "d", kPI / 180.0, UNITLABEL_DEGREE },
{ "deg", kPI / 180.0, UNITLABEL_DEGREE },
{ "'", kPI / (60.0 * 180.0), UNITLABEL_ARCMINUTE },
{ "\"", kPI / (3600.0 * 180.0), UNITLABEL_ARCSECOND },
{ "ym", 1.0e-24, UNITLABEL_YM },
{ "zm", 1.0e-21, UNITLABEL_ZM },
{ "am", 1.0e-18, UNITLABEL_AM },
{ "fm", 1.0e-15, UNITLABEL_FM },
{ "pm", 1.0e-12, UNITLABEL_PM },
{ "A", 1.0e-10, UNITLABEL_A },
{ "nm", 1.0e-9, UNITLABEL_NM },
{ "u", 1.0e-6, UNITLABEL_U },
{ "um", 1.0e-6, UNITLABEL_UM },
{ "ppt", kdInch / 72.27, UNITLABEL_PPT },
{ "pt", kdInch / 72.0, UNITLABEL_PT },
{ "mm", 1.0e-3, UNITLABEL_MM },
{ "p", kdInch / 6.0, UNITLABEL_P },
{ "cm", 1.0e-2, UNITLABEL_CM },
{ "in", kdInch, UNITLABEL_IN },
{ "dft", 0.03048, UNITLABEL_DFT },
{ "dm", 0.1, UNITLABEL_DM },
{ "li", 0.2011684 /* GDAL 0.20116684023368047 ? */, UNITLABEL_LI },
{ "sli", 0.201168402336805, UNITLABEL_SLI },
{ "sp", 0.2286, UNITLABEL_SP },
{ "ft", 0.3048, UNITLABEL_FT },
{ "sft", 1200.0 / 3937.0, UNITLABEL_SFT },
{ "yd", 0.9144, UNITLABEL_YD },
{ "syd", 0.914401828803658, UNITLABEL_SYD },
{ "m", 1.0, UNITLABEL_M },
{ "fath", 1.8288, UNITLABEL_FATH },
{ "rd", 5.02921, UNITLABEL_RD },
{ "dam", 10.0, UNITLABEL_DAM },
{ "dkm", 10.0, UNITLABEL_DKM },
{ "ch", 20.1168 /* GDAL: 2.0116684023368047 ? */, UNITLABEL_CH },
{ "sch", 20.1168402336805, UNITLABEL_SCH },
{ "hm", 100.0, UNITLABEL_HM },
{ "f", 201.168, UNITLABEL_F },
{ "km", 1000.0, UNITLABEL_KM },
{ "mi", 1609.344, UNITLABEL_MI },
{ "smi", 1609.34721869444, UNITLABEL_SMI },
{ "nmi", 1853.0, UNITLABEL_NMI },
{ "Mm", 1.0e+6, UNITLABEL_MEGAM },
{ "ls", kdLStoM, UNITLABEL_LS },
{ "Gm", 1.0e+9, UNITLABEL_GM },
{ "lm", kdLStoM * 60, UNITLABEL_LM },
{ "AU", 8.317 * kdLStoM * 60, UNITLABEL_AU },
{ "Tm", 1.0e+12, UNITLABEL_TM },
{ "lhr", 60.0 * 60.0 * kdLStoM, UNITLABEL_LHR },
{ "ld", 24 * 60.0 * 60.0 * kdLStoM, UNITLABEL_LD },
{ "Pm", 1.0e+15, UNITLABEL_PETAM },
{ "ly", kdLYtoM, UNITLABEL_LY },
{ "pc", 3.2616 * kdLYtoM, UNITLABEL_PC },
{ "Em", 1.0e+18, UNITLABEL_EXAM },
{ "kly", 1.0e+3 * kdLYtoM, UNITLABEL_KLY },
{ "kpc", 3.2616 * 1.0e+3 * kdLYtoM, UNITLABEL_KPC },
{ "Zm", 1.0e+21, UNITLABEL_ZETTAM },
{ "Mly", 1.0e+6 * kdLYtoM, UNITLABEL_MLY },
{ "Mpc", 3.2616 * 1.0e+6 * kdLYtoM, UNITLABEL_MPC },
{ "Ym", 1.0e+24, UNITLABEL_YOTTAM }
};
// ----------------------------------------------------------------
static bool approx_equal(double a, double b)
{
const double epsilon = 1e-5;
return (fabs(a-b) <= epsilon);
}
// ----------------------------------------------------------------
class LevellerRasterBand;
class LevellerDataset : public GDALPamDataset
{
friend class LevellerRasterBand;
friend class digital_axis;
int m_version;
char* m_pszFilename;
char* m_pszProjection;
//char m_szUnits[8];
char m_szElevUnits[8];
double m_dElevScale; // physical-to-logical scaling.
double m_dElevBase; // logical offset.
double m_adfTransform[6];
//double m_dMeasurePerPixel;
double m_dLogSpan[2];
VSILFILE* m_fp;
vsi_l_offset m_nDataOffset;
bool load_from_file(VSILFILE*, const char*);
bool locate_data(vsi_l_offset&, size_t&, VSILFILE*, const char*);
bool get(int&, VSILFILE*, const char*);
bool get(size_t& n, VSILFILE* fp, const char* psz)
{ return this->get((int&)n, fp, psz); }
bool get(double&, VSILFILE*, const char*);
bool get(char*, size_t, VSILFILE*, const char*);
bool write_header();
bool write_tag(const char*, int);
bool write_tag(const char*, size_t);
bool write_tag(const char*, double);
bool write_tag(const char*, const char*);
bool write_tag_start(const char*, size_t);
bool write(int);
bool write(size_t);
bool write(double);
bool write_byte(size_t);
const measurement_unit* get_uom(const char*) const;
const measurement_unit* get_uom(UNITLABEL) const;
const measurement_unit* get_uom(double) const;
bool convert_measure(double, double&, const char* pszUnitsFrom);
bool make_local_coordsys(const char* pszName, const char* pszUnits);
bool make_local_coordsys(const char* pszName, UNITLABEL);
const char* code_to_id(UNITLABEL) const;
UNITLABEL id_to_code(const char*) const;
UNITLABEL meter_measure_to_code(double) const;
bool compute_elev_scaling(const OGRSpatialReference&);
void raw_to_proj(double, double, double&, double&);
public:
LevellerDataset();
~LevellerDataset();
static GDALDataset* Open( GDALOpenInfo* );
static int Identify( GDALOpenInfo* );
static GDALDataset* Create( const char* pszFilename,
int nXSize, int nYSize, int nBands,
GDALDataType eType, char** papszOptions );
virtual CPLErr GetGeoTransform( double* );
virtual const char* GetProjectionRef(void);
virtual CPLErr SetGeoTransform( double* );
virtual CPLErr SetProjection(const char*);
};
class digital_axis
{
public:
digital_axis() : m_eStyle(LEV_DA_PIXEL_SIZED) {}
bool get(LevellerDataset& ds, VSILFILE* fp, int n)
{
char szTag[32];
sprintf(szTag, "coordsys_da%d_style", n);
if(!ds.get(m_eStyle, fp, szTag))
return false;
sprintf(szTag, "coordsys_da%d_fixedend", n);
if(!ds.get(m_fixedEnd, fp, szTag))
return false;
sprintf(szTag, "coordsys_da%d_v0", n);
if(!ds.get(m_d[0], fp, szTag))
return false;
sprintf(szTag, "coordsys_da%d_v1", n);
if(!ds.get(m_d[1], fp, szTag))
return false;
return true;
}
double origin(size_t pixels) const
{
if(m_fixedEnd == 1)
{
switch(m_eStyle)
{
case LEV_DA_SIZED:
return m_d[1] + m_d[0];
case LEV_DA_PIXEL_SIZED:
return m_d[1] + (m_d[0] * (pixels-1));
}
}
return m_d[0];
}
double scaling(size_t pixels) const
{
CPLAssert(pixels > 1);
if(m_eStyle == LEV_DA_PIXEL_SIZED)
return m_d[1 - m_fixedEnd];
return this->length(pixels) / (pixels - 1);
}
double length(int pixels) const
{
// Return the signed length of the axis.
switch(m_eStyle)
{
case LEV_DA_POSITIONED:
return m_d[1] - m_d[0];
case LEV_DA_SIZED:
return m_d[1 - m_fixedEnd];
case LEV_DA_PIXEL_SIZED:
return m_d[1 - m_fixedEnd] * (pixels-1);
}
CPLAssert(FALSE);
return 0.0;
}
protected:
int m_eStyle;
size_t m_fixedEnd;
double m_d[2];
};
/************************************************************************/
/* ==================================================================== */
/* LevellerRasterBand */
/* ==================================================================== */
/************************************************************************/
class LevellerRasterBand : public GDALPamRasterBand
{
friend class LevellerDataset;
float* m_pLine;
bool m_bFirstTime;
public:
LevellerRasterBand(LevellerDataset*);
~LevellerRasterBand();
// Geomeasure support.
virtual const char* GetUnitType();
virtual double GetScale(int* pbSuccess = NULL);
virtual double GetOffset(int* pbSuccess = NULL);
virtual CPLErr IReadBlock( int, int, void * );
virtual CPLErr IWriteBlock( int, int, void * );
virtual CPLErr SetUnitType( const char* );
};
/************************************************************************/
/* LevellerRasterBand() */
/************************************************************************/
LevellerRasterBand::LevellerRasterBand( LevellerDataset *poDS )
:
m_pLine(NULL),
m_bFirstTime(true)
{
this->poDS = poDS;
this->nBand = 1;
eDataType = GDT_Float32;
nBlockXSize = poDS->GetRasterXSize();
nBlockYSize = 1;//poDS->GetRasterYSize();
m_pLine = (float*)CPLMalloc(sizeof(float) * nBlockXSize);
}
LevellerRasterBand::~LevellerRasterBand()
{
if(m_pLine != NULL)
CPLFree(m_pLine);
}
/************************************************************************/
/* IWriteBlock() */
/************************************************************************/
CPLErr LevellerRasterBand::IWriteBlock
(
CPL_UNUSED int nBlockXOff,
int nBlockYOff,
void* pImage
)
{
CPLAssert( nBlockXOff == 0 );
CPLAssert( pImage != NULL );
CPLAssert( m_pLine != NULL );
/* #define sgn(_n) ((_n) < 0 ? -1 : ((_n) > 0 ? 1 : 0) )
#define sround(_f) \
(int)((_f) + (0.5 * sgn(_f)))
*/
const size_t pixelsize = sizeof(float);
LevellerDataset& ds = *(LevellerDataset*)poDS;
if(m_bFirstTime)
{
m_bFirstTime = false;
if(!ds.write_header())
return CE_Failure;
ds.m_nDataOffset = VSIFTellL(ds.m_fp);
}
const size_t rowbytes = nBlockXSize * pixelsize;
const float* pfImage = (float*)pImage;
if(0 == VSIFSeekL(
ds.m_fp, ds.m_nDataOffset + nBlockYOff * rowbytes,
SEEK_SET))
{
for(size_t x = 0; x < (size_t)nBlockXSize; x++)
{
// Convert logical elevations to physical.
m_pLine[x] = (float)
((pfImage[x] - ds.m_dElevBase) / ds.m_dElevScale);
}
#ifdef CPL_MSB
GDALSwapWords( m_pLine, pixelsize, nBlockXSize, pixelsize );
#endif
if(1 == VSIFWriteL(m_pLine, rowbytes, 1, ds.m_fp))
return CE_None;
}
return CE_Failure;
}
CPLErr LevellerRasterBand::SetUnitType( const char* psz )
{
LevellerDataset& ds = *(LevellerDataset*)poDS;
if(strlen(psz) >= sizeof(ds.m_szElevUnits))
return CE_Failure;
strcpy(ds.m_szElevUnits, psz);
return CE_None;
}
/************************************************************************/
/* IReadBlock() */
/************************************************************************/
CPLErr LevellerRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
int nBlockYOff,
void* pImage )
{
CPLAssert( sizeof(float) == sizeof(GInt32) );
CPLAssert( nBlockXOff == 0 );
CPLAssert( pImage != NULL );
LevellerDataset *poGDS = (LevellerDataset *) poDS;
/* -------------------------------------------------------------------- */
/* Seek to scanline. */
/* -------------------------------------------------------------------- */
const size_t rowbytes = nBlockXSize * sizeof(float);
if(0 != VSIFSeekL(
poGDS->m_fp,
poGDS->m_nDataOffset + nBlockYOff * rowbytes,
SEEK_SET))
{
CPLError( CE_Failure, CPLE_FileIO,
".bt Seek failed:%s", VSIStrerror( errno ) );
return CE_Failure;
}
/* -------------------------------------------------------------------- */
/* Read the scanline into the image buffer. */
/* -------------------------------------------------------------------- */
if( VSIFReadL( pImage, rowbytes, 1, poGDS->m_fp ) != 1 )
{
CPLError( CE_Failure, CPLE_FileIO,
"Leveller read failed:%s", VSIStrerror( errno ) );
return CE_Failure;
}
/* -------------------------------------------------------------------- */
/* Swap on MSB platforms. */
/* -------------------------------------------------------------------- */
#ifdef CPL_MSB
GDALSwapWords( pImage, 4, nRasterXSize, 4 );
#endif
/* -------------------------------------------------------------------- */
/* Convert from legacy-format fixed-point if necessary. */
/* -------------------------------------------------------------------- */
float* pf = (float*)pImage;
if(poGDS->m_version < 6)
{
GInt32* pi = (int*)pImage;
for(size_t i = 0; i < (size_t)nBlockXSize; i++)
pf[i] = (float)pi[i] / 65536;
}
#if 0
/* -------------------------------------------------------------------- */
/* Convert raw elevations to realworld elevs. */
/* -------------------------------------------------------------------- */
for(size_t i = 0; i < nBlockXSize; i++)
pf[i] *= poGDS->m_dWorldscale; //this->GetScale();
#endif
return CE_None;
}
/************************************************************************/
/* GetUnitType() */
/************************************************************************/
const char *LevellerRasterBand::GetUnitType()
{
// Return elevation units.
LevellerDataset *poGDS = (LevellerDataset *) poDS;
return poGDS->m_szElevUnits;
}
/************************************************************************/
/* GetScale() */
/************************************************************************/
double LevellerRasterBand::GetScale(int* pbSuccess)
{
LevellerDataset *poGDS = (LevellerDataset *) poDS;
if(pbSuccess != NULL)
*pbSuccess = TRUE;
return poGDS->m_dElevScale;
}
/************************************************************************/
/* GetOffset() */
/************************************************************************/
double LevellerRasterBand::GetOffset(int* pbSuccess)
{
LevellerDataset *poGDS = (LevellerDataset *) poDS;
if(pbSuccess != NULL)
*pbSuccess = TRUE;
return poGDS->m_dElevBase;
}
/************************************************************************/
/* ==================================================================== */
/* LevellerDataset */
/* ==================================================================== */
/************************************************************************/
/************************************************************************/
/* LevellerDataset() */
/************************************************************************/
LevellerDataset::LevellerDataset()
{
m_fp = NULL;
m_pszProjection = NULL;
m_pszFilename = NULL;
}
/************************************************************************/
/* ~LevellerDataset() */
/************************************************************************/
LevellerDataset::~LevellerDataset()
{
FlushCache();
CPLFree(m_pszProjection);
CPLFree(m_pszFilename);
if( m_fp != NULL )
VSIFCloseL( m_fp );
}
static double degrees_to_radians(double d)
{
return (d * 0.017453292);
}
static double average(double a, double b)
{
return 0.5 * (a + b);
}
void LevellerDataset::raw_to_proj(double x, double y, double& xp, double& yp)
{
xp = x * m_adfTransform[1] + m_adfTransform[0];
yp = y * m_adfTransform[5] + m_adfTransform[3];
}
bool LevellerDataset::compute_elev_scaling
(
const OGRSpatialReference& sr
)
{
const char* pszGroundUnits;
if(!sr.IsGeographic())
{
// For projected or local CS, the elev scale is
// the average ground scale.
m_dElevScale = average(m_adfTransform[1], m_adfTransform[5]);
const double dfLinear = sr.GetLinearUnits();
const measurement_unit* pu = this->get_uom(dfLinear);
if(pu == NULL)
return false;
pszGroundUnits = pu->pszID;
}
else
{
pszGroundUnits = "m";
const double kdEarthCircumPolar = 40007849;
const double kdEarthCircumEquat = 40075004;
double xr, yr;
xr = 0.5 * this->nRasterXSize;
yr = 0.5 * this->nRasterYSize;
double xg[2], yg[2];
this->raw_to_proj(xr, yr, xg[0], yg[0]);
this->raw_to_proj(xr+1, yr+1, xg[1], yg[1]);
// The earths' circumference shrinks using a sin()
// curve as we go up in latitude.
const double dLatCircum = kdEarthCircumEquat
* sin(degrees_to_radians(90.0 - yg[0]));
// Derive meter distance between geolongitudes
// in xg[0] and xg[1].
double dx = fabs(xg[1] - xg[0]) / 360.0 * dLatCircum;
double dy = fabs(yg[1] - yg[0]) / 360.0 * kdEarthCircumPolar;
m_dElevScale = average(dx, dy);
}
m_dElevBase = m_dLogSpan[0];
// Convert from ground units to elev units.
const measurement_unit* puG = this->get_uom(pszGroundUnits);
const measurement_unit* puE = this->get_uom(m_szElevUnits);
if(puG == NULL || puE == NULL)
return false;
const double g_to_e = puG->dScale / puE->dScale;
m_dElevScale *= g_to_e;
return true;
}
bool LevellerDataset::write_header()
{
char szHeader[5];
strcpy(szHeader, "trrn");
szHeader[4] = 7; // TER v7 introduced w/ Lev 2.6.
if(1 != VSIFWriteL(szHeader, 5, 1, m_fp)
|| !this->write_tag("hf_w", (size_t)nRasterXSize)
|| !this->write_tag("hf_b", (size_t)nRasterYSize))
{
CPLError( CE_Failure, CPLE_FileIO, "Could not write header" );
return false;
}
m_dElevBase = 0.0;
m_dElevScale = 1.0;
if(m_pszProjection == NULL || m_pszProjection[0] == 0)
{
this->write_tag("csclass", LEV_COORDSYS_RASTER);
}
else
{
this->write_tag("coordsys_wkt", m_pszProjection);
const UNITLABEL units_elev = this->id_to_code(m_szElevUnits);
const int bHasECS =
(units_elev != UNITLABEL_PIXEL && units_elev != UNITLABEL_UNKNOWN);
this->write_tag("coordsys_haselevm", bHasECS);
OGRSpatialReference sr(m_pszProjection);
if(bHasECS)
{
if(!this->compute_elev_scaling(sr))
return false;
// Raw-to-real units scaling.
this->write_tag("coordsys_em_scale", m_dElevScale);
//elev offset, in real units.
this->write_tag("coordsys_em_base", m_dElevBase);
this->write_tag("coordsys_em_units", units_elev);
}
if(sr.IsLocal())
{
this->write_tag("csclass", LEV_COORDSYS_LOCAL);
const double dfLinear = sr.GetLinearUnits();
const int n = this->meter_measure_to_code(dfLinear);
this->write_tag("coordsys_units", n);
}
else
{
this->write_tag("csclass", LEV_COORDSYS_GEO);
}
if( m_adfTransform[2] != 0.0 || m_adfTransform[4] != 0.0)
{
CPLError( CE_Failure, CPLE_IllegalArg,
"Cannot handle rotated geotransform" );
return false;
}
// todo: GDAL gridpost spacing is based on extent / rastersize
// instead of extent / (rastersize-1) like Leveller.
// We need to look into this and adjust accordingly.
// Write north-south digital axis.
this->write_tag("coordsys_da0_style", LEV_DA_PIXEL_SIZED);
this->write_tag("coordsys_da0_fixedend", 0);
this->write_tag("coordsys_da0_v0", m_adfTransform[3]);
this->write_tag("coordsys_da0_v1", m_adfTransform[5]);
// Write east-west digital axis.
this->write_tag("coordsys_da1_style", LEV_DA_PIXEL_SIZED);
this->write_tag("coordsys_da1_fixedend", 0);
this->write_tag("coordsys_da1_v0", m_adfTransform[0]);
this->write_tag("coordsys_da1_v1", m_adfTransform[1]);
}
this->write_tag_start("hf_data",
sizeof(float) * nRasterXSize * nRasterYSize);
return true;
}
/************************************************************************/
/* SetGeoTransform() */
/************************************************************************/
CPLErr LevellerDataset::SetGeoTransform( double *padfGeoTransform )
{
memcpy(m_adfTransform, padfGeoTransform,
sizeof(m_adfTransform));
return CE_None;
}
/************************************************************************/
/* SetProjection() */
/************************************************************************/
CPLErr LevellerDataset::SetProjection( const char * pszNewProjection )
{
if(m_pszProjection != NULL)
CPLFree(m_pszProjection);
m_pszProjection = CPLStrdup(pszNewProjection);
return CE_None;
}
/************************************************************************/
/* Create() */
/************************************************************************/
GDALDataset* LevellerDataset::Create
(
const char* pszFilename,
int nXSize, int nYSize, int nBands,
GDALDataType eType, char** papszOptions
)
{
if(nBands != 1)
{
CPLError( CE_Failure, CPLE_IllegalArg, "Band count must be 1" );
return NULL;
}
if(eType != GDT_Float32)
{
CPLError( CE_Failure, CPLE_IllegalArg, "Pixel type must be Float32" );
return NULL;
}
if(nXSize < 2 || nYSize < 2)
{
CPLError( CE_Failure, CPLE_IllegalArg, "One or more raster dimensions too small" );
return NULL;
}
LevellerDataset* poDS = new LevellerDataset;
poDS->eAccess = GA_Update;
poDS->m_pszFilename = CPLStrdup(pszFilename);
poDS->m_fp = VSIFOpenL( pszFilename, "wb+" );
if( poDS->m_fp == NULL )
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Attempt to create file `%s' failed.",
pszFilename );
delete poDS;
return NULL;
}
// Header will be written the first time IWriteBlock
// is called.
poDS->nRasterXSize = nXSize;
poDS->nRasterYSize = nYSize;
const char* pszValue = CSLFetchNameValue(
papszOptions,"MINUSERPIXELVALUE");
if( pszValue != NULL )
poDS->m_dLogSpan[0] = CPLAtof( pszValue );
else
{
delete poDS;
CPLError( CE_Failure, CPLE_IllegalArg,
"MINUSERPIXELVALUE must be specified." );
return NULL;
}
pszValue = CSLFetchNameValue(
papszOptions,"MAXUSERPIXELVALUE");
if( pszValue != NULL )
poDS->m_dLogSpan[1] = CPLAtof( pszValue );
if(poDS->m_dLogSpan[1] < poDS->m_dLogSpan[0])
{
double t = poDS->m_dLogSpan[0];
poDS->m_dLogSpan[0] = poDS->m_dLogSpan[1];
poDS->m_dLogSpan[1] = t;
}
// --------------------------------------------------------------------
// Instance a band.
// --------------------------------------------------------------------
poDS->SetBand( 1, new LevellerRasterBand( poDS ) );
return poDS;
}
bool LevellerDataset::write_byte(size_t n)
{
unsigned char uch = (unsigned char)n;
return (1 == VSIFWriteL(&uch, 1, 1, m_fp));
}
bool LevellerDataset::write(int n)
{
CPL_LSBPTR32(&n);
return (1 == VSIFWriteL(&n, sizeof(n), 1, m_fp));
}
bool LevellerDataset::write(size_t n)
{
CPL_LSBPTR32(&n);
return (1 == VSIFWriteL(&n, sizeof(n), 1, m_fp));
}
bool LevellerDataset::write(double d)
{
CPL_LSBPTR64(&d);
return (1 == VSIFWriteL(&d, sizeof(d), 1, m_fp));
}
bool LevellerDataset::write_tag_start(const char* pszTag, size_t n)
{
if(this->write_byte(strlen(pszTag)))
{
return (1 == VSIFWriteL(pszTag, strlen(pszTag), 1, m_fp)
&& this->write(n));
}
return false;
}
bool LevellerDataset::write_tag(const char* pszTag, int n)
{
return (this->write_tag_start(pszTag, sizeof(n))
&& this->write(n));
}
bool LevellerDataset::write_tag(const char* pszTag, size_t n)
{
return (this->write_tag_start(pszTag, sizeof(n))
&& this->write(n));
}
bool LevellerDataset::write_tag(const char* pszTag, double d)
{
return (this->write_tag_start(pszTag, sizeof(d))
&& this->write(d));
}
bool LevellerDataset::write_tag(const char* pszTag, const char* psz)
{
CPLAssert(strlen(pszTag) <= kMaxTagNameLen);
char sz[kMaxTagNameLen + 1];
sprintf(sz, "%sl", pszTag);
const size_t len = strlen(psz);
if(len > 0 && this->write_tag(sz, len))
{
sprintf(sz, "%sd", pszTag);
this->write_tag_start(sz, len);
return (1 == VSIFWriteL(psz, len, 1, m_fp));
}
return false;
}
bool LevellerDataset::locate_data(vsi_l_offset& offset, size_t& len, VSILFILE* fp, const char* pszTag)
{
// Locate the file offset of the desired tag's data.
// If it is not available, return false.
// If the tag is found, leave the filemark at the
// start of its data.
if(0 != VSIFSeekL(fp, 5, SEEK_SET))
return false;
const int kMaxDescLen = 64;
for(;;)
{
unsigned char c;
if(1 != VSIFReadL(&c, sizeof(c), 1, fp))
return false;
const size_t descriptorLen = c;
if(descriptorLen == 0 || descriptorLen > (size_t)kMaxDescLen)
return false;
char descriptor[kMaxDescLen+1];
if(1 != VSIFReadL(descriptor, descriptorLen, 1, fp))
return false;
GUInt32 datalen;
if(1 != VSIFReadL(&datalen, sizeof(datalen), 1, fp))
return false;
datalen = CPL_LSBWORD32(datalen);
descriptor[descriptorLen] = 0;
if(str_equal(descriptor, pszTag))
{
len = (size_t)datalen;
offset = VSIFTellL(fp);
return true;
}
else
{
// Seek to next tag.
if(0 != VSIFSeekL(fp, (vsi_l_offset)datalen, SEEK_CUR))
return false;
}
}
}
/************************************************************************/
/* get() */
/************************************************************************/
bool LevellerDataset::get(int& n, VSILFILE* fp, const char* psz)
{
vsi_l_offset offset;
size_t len;
if(this->locate_data(offset, len, fp, psz))
{
GInt32 value;
if(1 == VSIFReadL(&value, sizeof(value), 1, fp))
{
CPL_LSBPTR32(&value);
n = (int)value;
return true;
}
}
return false;
}
/************************************************************************/
/* get() */
/************************************************************************/
bool LevellerDataset::get(double& d, VSILFILE* fp, const char* pszTag)
{
vsi_l_offset offset;
size_t len;
if(this->locate_data(offset, len, fp, pszTag))
{
if(1 == VSIFReadL(&d, sizeof(d), 1, fp))
{
CPL_LSBPTR64(&d);
return true;
}
}
return false;
}
/************************************************************************/
/* get() */
/************************************************************************/
bool LevellerDataset::get(char* pszValue, size_t maxchars, VSILFILE* fp, const char* pszTag)
{
char szTag[65];
// We can assume 8-bit encoding, so just go straight
// to the *_d tag.
sprintf(szTag, "%sd", pszTag);
vsi_l_offset offset;
size_t len;
if(this->locate_data(offset, len, fp, szTag))
{
if(len > maxchars)
return false;
if(1 == VSIFReadL(pszValue, len, 1, fp))
{
pszValue[len] = 0; // terminate C-string
return true;
}
}
return false;
}
UNITLABEL LevellerDataset::meter_measure_to_code(double dM) const
{
// Convert a meter conversion factor to its UOM OEM code.
// If the factor is close to the approximation margin, then
// require exact equality, otherwise be loose.
const measurement_unit* pu = this->get_uom(dM);
return (pu != NULL ? pu->oemCode : UNITLABEL_UNKNOWN);
}
UNITLABEL LevellerDataset::id_to_code(const char* pszUnits) const
{
// Convert a readable UOM to its OEM code.
const measurement_unit* pu = this->get_uom(pszUnits);
return (pu != NULL ? pu->oemCode : UNITLABEL_UNKNOWN);
}
const char* LevellerDataset::code_to_id(UNITLABEL code) const
{
// Convert a measurement unit's OEM ID to its readable ID.
const measurement_unit* pu = this->get_uom(code);
return (pu != NULL ? pu->pszID : NULL);
}
const measurement_unit* LevellerDataset::get_uom(const char* pszUnits) const
{
for(size_t i = 0; i < array_size(kUnits); i++)
{
if(strcmp(pszUnits, kUnits[i].pszID) == 0)
return &kUnits[i];
}
CPLError( CE_Failure, CPLE_AppDefined,
"Unknown measurement units: %s", pszUnits );
return NULL;
}
const measurement_unit* LevellerDataset::get_uom(UNITLABEL code) const
{
for(size_t i = 0; i < array_size(kUnits); i++)
{
if(kUnits[i].oemCode == code)
return &kUnits[i];
}
CPLError( CE_Failure, CPLE_AppDefined,
"Unknown measurement unit code: %08x", code );
return NULL;
}
const measurement_unit* LevellerDataset::get_uom(double dM) const
{
for(size_t i = kFirstLinearMeasureIdx; i < array_size(kUnits); i++)
{
if(dM >= 1.0e-4)
{
if(approx_equal(dM, kUnits[i].dScale))
return &kUnits[i];
}
else if(dM == kUnits[i].dScale)
return &kUnits[i];
}
CPLError( CE_Failure, CPLE_AppDefined,
"Unknown measurement conversion factor: %f", dM );
return NULL;
}
/************************************************************************/
/* convert_measure() */
/************************************************************************/
bool LevellerDataset::convert_measure
(
double d,
double& dResult,
const char* pszSpace
)
{
// Convert a measure to meters.
for(size_t i = kFirstLinearMeasureIdx; i < array_size(kUnits); i++)
{
if(str_equal(pszSpace, kUnits[i].pszID))
{
dResult = d * kUnits[i].dScale;
return true;
}
}
CPLError( CE_Failure, CPLE_FileIO,
"Unknown linear measurement unit: '%s'", pszSpace );
return false;
}
bool LevellerDataset::make_local_coordsys(const char* pszName, const char* pszUnits)
{
OGRSpatialReference sr;
sr.SetLocalCS(pszName);
double d;
return ( this->convert_measure(1.0, d, pszUnits)
&& OGRERR_NONE == sr.SetLinearUnits(pszUnits, d)
&& OGRERR_NONE == sr.exportToWkt(&m_pszProjection) );
}
bool LevellerDataset::make_local_coordsys(const char* pszName, UNITLABEL code)
{
const char* pszUnitID = this->code_to_id(code);
return ( pszUnitID != NULL
&& this->make_local_coordsys(pszName, pszUnitID));
}
/************************************************************************/
/* load_from_file() */
/************************************************************************/
bool LevellerDataset::load_from_file(VSILFILE* file, const char* pszFilename)
{
// get hf dimensions
if(!this->get(nRasterXSize, file, "hf_w"))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Cannot determine heightfield width." );
return false;
}
if(!this->get(nRasterYSize, file, "hf_b"))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Cannot determine heightfield breadth." );
return false;
}
if(nRasterXSize < 2 || nRasterYSize < 2)
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Heightfield raster dimensions too small." );
return false;
}
// Record start of pixel data
size_t datalen;
if(!this->locate_data(m_nDataOffset, datalen, file, "hf_data"))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Cannot locate elevation data." );
return false;
}
// Sanity check: do we have enough pixels?
if(datalen != nRasterXSize * nRasterYSize * sizeof(float))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"File does not have enough data." );
return false;
}
// Defaults for raster coordsys.
m_adfTransform[0] = 0.0;
m_adfTransform[1] = 1.0;
m_adfTransform[2] = 0.0;
m_adfTransform[3] = 0.0;
m_adfTransform[4] = 0.0;
m_adfTransform[5] = 1.0;
m_dElevScale = 1.0;
m_dElevBase = 0.0;
strcpy(m_szElevUnits, "");
if(m_version == 7)
{
// Read coordsys info.
int csclass = LEV_COORDSYS_RASTER;
(void)this->get(csclass, file, "csclass");
if(csclass != LEV_COORDSYS_RASTER)
{
// Get projection details and units.
CPLAssert(m_pszProjection == NULL);
if(csclass == LEV_COORDSYS_LOCAL)
{
UNITLABEL unitcode;
//char szLocalUnits[8];
int unitcode_int;
if(!this->get(unitcode_int, file, "coordsys_units"))
unitcode_int = UNITLABEL_M;
unitcode = (UNITLABEL) unitcode_int;
if(!this->make_local_coordsys("Leveller", unitcode))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Cannot define local coordinate system." );
return false;
}
}
else if(csclass == LEV_COORDSYS_GEO)
{
char szWKT[1024];
if(!this->get(szWKT, 1023, file, "coordsys_wkt"))
return 0;
m_pszProjection = (char*)CPLMalloc(strlen(szWKT) + 1);
strcpy(m_pszProjection, szWKT);
}
else
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Unknown coordinate system type in %s.",
pszFilename );
return false;
}
// Get ground extents.
digital_axis axis_ns, axis_ew;
if(axis_ns.get(*this, file, 0)
&& axis_ew.get(*this, file, 1))
{
m_adfTransform[0] = axis_ew.origin(nRasterXSize);
m_adfTransform[1] = axis_ew.scaling(nRasterXSize);
m_adfTransform[2] = 0.0;
m_adfTransform[3] = axis_ns.origin(nRasterYSize);
m_adfTransform[4] = 0.0;
m_adfTransform[5] = axis_ns.scaling(nRasterYSize);
}
}
// Get vertical (elev) coordsys.
int bHasVertCS = FALSE;
if(this->get(bHasVertCS, file, "coordsys_haselevm") && bHasVertCS)
{
this->get(m_dElevScale, file, "coordsys_em_scale");
this->get(m_dElevBase, file, "coordsys_em_base");
UNITLABEL unitcode;
int unitcode_int;
if(this->get(unitcode_int, file, "coordsys_em_units"))
{
unitcode = (UNITLABEL) unitcode_int;
const char* pszUnitID = this->code_to_id(unitcode);
if(pszUnitID != NULL)
strcpy(m_szElevUnits, pszUnitID);
else
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Unknown OEM elevation unit of measure (%d)",
unitcode );
return false;
}
}
// datum and localcs are currently unused.
}
}
else
{
// Legacy files use world units.
char szWorldUnits[32];
strcpy(szWorldUnits, "m");
double dWorldscale = 1.0;
if(this->get(dWorldscale, file, "hf_worldspacing"))
{
//m_bHasWorldscale = true;
if(this->get(szWorldUnits, sizeof(szWorldUnits)-1, file,
"hf_worldspacinglabel"))
{
// Drop long name, if present.
char* p = strchr(szWorldUnits, ' ');
if(p != NULL)
*p = 0;
}
#if 0
// If the units are something besides m/ft/sft,
// then convert them to meters.
if(!str_equal("m", szWorldUnits)
&& !str_equal("ft", szWorldUnits)
&& !str_equal("sft", szWorldUnits))
{
dWorldscale = this->convert_measure(dWorldscale, szWorldUnits);
strcpy(szWorldUnits, "m");
}
#endif
// Our extents are such that the origin is at the
// center of the heightfield.
m_adfTransform[0] = -0.5 * dWorldscale * (nRasterXSize-1);
m_adfTransform[3] = -0.5 * dWorldscale * (nRasterYSize-1);
m_adfTransform[1] = dWorldscale;
m_adfTransform[5] = dWorldscale;
}
m_dElevScale = dWorldscale; // this was 1.0 before because
// we were converting to real elevs ourselves, but
// some callers may want both the raw pixels and the
// transform to get real elevs.
if(!this->make_local_coordsys("Leveller world space", szWorldUnits))
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Cannot define local coordinate system." );
return false;
}
}
return true;
}
/************************************************************************/
/* GetProjectionRef() */
/************************************************************************/
const char* LevellerDataset::GetProjectionRef(void)
{
return (m_pszProjection == NULL ? "" : m_pszProjection);
}
/************************************************************************/
/* GetGeoTransform() */
/************************************************************************/
CPLErr LevellerDataset::GetGeoTransform(double* padfTransform)
{
memcpy(padfTransform, m_adfTransform, sizeof(m_adfTransform));
return CE_None;
}
/************************************************************************/
/* Identify() */
/************************************************************************/
int LevellerDataset::Identify( GDALOpenInfo * poOpenInfo )
{
if( poOpenInfo->nHeaderBytes < 4 )
return FALSE;
return EQUALN((const char *) poOpenInfo->pabyHeader, "trrn", 4);
}
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *LevellerDataset::Open( GDALOpenInfo * poOpenInfo )
{
// The file should have at least 5 header bytes
// and hf_w, hf_b, and hf_data tags.
#ifdef DEBUG
#endif
if( poOpenInfo->nHeaderBytes < 5+13+13+16 )
return NULL;
if( !LevellerDataset::Identify(poOpenInfo))
return NULL;
const int version = poOpenInfo->pabyHeader[4];
if(version < 4 || version > 7)
return NULL;
/* -------------------------------------------------------------------- */
/* Create a corresponding GDALDataset. */
/* -------------------------------------------------------------------- */
LevellerDataset* poDS = new LevellerDataset();
poDS->m_version = version;
// Reopen for large file access.
if( poOpenInfo->eAccess == GA_Update )
poDS->m_fp = VSIFOpenL( poOpenInfo->pszFilename, "rb+" );
else
poDS->m_fp = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
if( poDS->m_fp == NULL )
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Failed to re-open %s within Leveller driver.",
poOpenInfo->pszFilename );
return NULL;
}
poDS->eAccess = poOpenInfo->eAccess;
/* -------------------------------------------------------------------- */
/* Read the file. */
/* -------------------------------------------------------------------- */
if( !poDS->load_from_file( poDS->m_fp, poOpenInfo->pszFilename ) )
{
delete poDS;
return NULL;
}
/* -------------------------------------------------------------------- */
/* Create band information objects. */
/* -------------------------------------------------------------------- */
poDS->SetBand( 1, new LevellerRasterBand( poDS ));
poDS->SetMetadataItem( GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT );
/* -------------------------------------------------------------------- */
/* Initialize any PAM information. */
/* -------------------------------------------------------------------- */
poDS->SetDescription( poOpenInfo->pszFilename );
poDS->TryLoadXML();
/* -------------------------------------------------------------------- */
/* Check for external overviews. */
/* -------------------------------------------------------------------- */
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename, poOpenInfo->GetSiblingFiles() );
return( poDS );
}
#else
// stub so that module compiles
class LevellerDataset : public GDALPamDataset
{
public:
static GDALDataset* Open( GDALOpenInfo* ) { return NULL; }
};
#endif
/************************************************************************/
/* GDALRegister_Leveller() */
/************************************************************************/
void GDALRegister_Leveller()
{
GDALDriver *poDriver;
if( GDALGetDriverByName( "Leveller" ) == NULL )
{
poDriver = new GDALDriver();
poDriver->SetDescription( "Leveller" );
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION,
"ter" );
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
"Leveller heightfield" );
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
"frmt_leveller.html" );
#if GDAL_VERSION_NUM >= 1500
poDriver->pfnIdentify = LevellerDataset::Identify;
#endif
poDriver->pfnOpen = LevellerDataset::Open;
poDriver->pfnCreate = LevellerDataset::Create;
GetGDALDriverManager()->RegisterDriver( poDriver );
}
}