LCOV - code coverage report
Current view: directory - frmts/mrsid_lidar - gdal_MG4Lidar.cpp (source / functions) Found Hit Coverage
Test: gdal_filtered.info Lines: 411 282 68.6 %
Date: 2012-12-26 Functions: 44 20 45.5 %

       1                 : /******************************************************************************
       2                 : * Project:  MG4 Lidar GDAL Plugin
       3                 : * Purpose:  Provide an orthographic view of MG4-encoded Lidar dataset
       4                 : *                   for use with LizardTech Lidar SDK version 1.1.0
       5                 : * Author:   Michael Rosen, mrosen@lizardtech.com
       6                 : *
       7                 : ******************************************************************************
       8                 : * Copyright (c) 2010, LizardTech
       9                 : * All rights reserved.
      10                 : 
      11                 : * Redistribution and use in source and binary forms, with or without 
      12                 : * modification, are permitted provided that the following conditions are met:
      13                 : *
      14                 : *   Redistributions of source code must retain the above copyright notice, 
      15                 : *   this list of conditions and the following disclaimer.
      16                 : * 
      17                 : *   Redistributions in binary form must reproduce the above copyright notice, 
      18                 : *   this list of conditions and the following disclaimer in the documentation
      19                 : *   and/or other materials provided with the distribution.
      20                 : *
      21                 : *   Neither the name of the LizardTech nor the names of its contributors may 
      22                 : *   be used to endorse or promote products derived from this software without 
      23                 : *   specific prior written permission.
      24                 : *
      25                 : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
      26                 : * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
      27                 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
      28                 : * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
      29                 : * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
      30                 : * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
      31                 : * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
      32                 : * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
      33                 : * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
      34                 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
      35                 : * POSSIBILITY OF SUCH DAMAGE.
      36                 : ****************************************************************************/
      37                 : #include "lidar/MG4PointReader.h"
      38                 : #include "lidar/FileIO.h"
      39                 : #include "lidar/Error.h"
      40                 : #include "lidar/Version.h"
      41                 : #include <float.h>
      42                 : LT_USE_LIDAR_NAMESPACE
      43                 : 
      44                 : #include "gdal_pam.h"
      45                 : // #include "gdal_alg.h" // 1.6 and later have gridding algorithms
      46                 : 
      47                 : CPL_C_START
      48                 : //void __declspec(dllexport) GDALRegister_MG4Lidar(void);
      49                 : void CPL_DLL GDALRegister_MG4Lidar(void);
      50                 : CPL_C_END
      51                 : 
      52                 : /************************************************************************/
      53                 : /* ==================================================================== */
      54                 : /*        MG4LidarDataset       */
      55                 : /* ==================================================================== */
      56                 : /************************************************************************/
      57                 : 
      58                 : // Resolution Ratio between adjacent levels.
      59                 : #define RESOLUTION_RATIO 2.0
      60                 : 
      61                 : class MG4LidarRasterBand;
      62                 : class CropableMG4PointReader : public MG4PointReader
      63                 : {
      64                 :    CONCRETE_OBJECT(CropableMG4PointReader);
      65               1 :    void init (IO *io, Bounds *bounds)
      66                 :    {
      67               1 :       MG4PointReader::init(io);
      68               1 :       if (bounds != NULL)
      69               1 :          setBounds(*bounds);
      70               1 :    }
      71                 : };
      72               1 : CropableMG4PointReader::CropableMG4PointReader() : MG4PointReader() {};   
      73               1 : CropableMG4PointReader::~CropableMG4PointReader() {};   
      74                 : 
      75               1 : IMPLEMENT_OBJECT_CREATE(CropableMG4PointReader);
      76                 : 
      77                 : static double MaxRasterSize = 2048.0;
      78                 : static double MaxBlockSideSize = 1024.0;
      79                 : class MG4LidarDataset : public GDALPamDataset
      80                 : {
      81                 : friend class MG4LidarRasterBand;
      82                 : public:
      83                 :    MG4LidarDataset();
      84                 :    ~MG4LidarDataset();
      85                 :    static GDALDataset *Open( GDALOpenInfo * );
      86                 :    CPLErr   GetGeoTransform( double * padfTransform );
      87                 :    const char *GetProjectionRef();
      88                 : protected:
      89                 :    MG4PointReader *reader;
      90                 :    FileIO *fileIO;
      91                 :    CPLErr OpenZoomLevel( int Zoom );
      92                 :    PointInfo requiredChannels;
      93                 :    int nOverviewCount;
      94                 :    MG4LidarDataset **papoOverviewDS;
      95                 :    CPLXMLNode *poXMLPCView;
      96                 :    bool ownsXML;
      97                 :    int nBlockXSize, nBlockYSize;
      98                 :    int iLevel;
      99                 : };
     100                 : 
     101                 : /* ========================================  */
     102                 : /*                            MG4LidarRasterBand                                          */
     103                 : /* ========================================  */
     104                 : 
     105                 : class MG4LidarRasterBand : public GDALPamRasterBand
     106                 : {
     107                 :    friend class MG4LidarDataset;
     108                 : 
     109                 : public:
     110                 : 
     111                 :    MG4LidarRasterBand( MG4LidarDataset *, int, CPLXMLNode *, const char * );
     112                 :    ~MG4LidarRasterBand();
     113                 : 
     114                 :    virtual CPLErr GetStatistics( int bApproxOK, int bForce, double *pdfMin, double *pdfMax, double *pdfMean, double *padfStdDev );
     115                 :    virtual int GetOverviewCount();
     116                 :    virtual GDALRasterBand * GetOverview( int i );
     117                 :    virtual CPLErr IReadBlock( int, int, void * );
     118                 :    virtual double GetNoDataValue( int *pbSuccess = NULL );
     119                 : 
     120                 :    protected:
     121                 :    double getMaxValue();
     122                 :    double nodatavalue;
     123                 :    virtual const bool ElementPassesFilter(const PointData &, size_t);
     124                 :    template<typename DTYPE>
     125                 :    CPLErr   doReadBlock(int, int, void *);
     126                 :    CPLXMLNode *poxmlBand;
     127                 :    char **papszFilterClassCodes;
     128                 :    char **papszFilterReturnNums;
     129                 :    const char * Aggregation;
     130                 :    CPLString ChannelName;
     131                 : };
     132                 : 
     133                 : 
     134                 : /************************************************************************/
     135                 : /*                           MG4LidarRasterBand()                            */
     136                 : /************************************************************************/
     137                 : 
     138               2 : MG4LidarRasterBand::MG4LidarRasterBand( MG4LidarDataset *pods, int nband, CPLXMLNode *xmlBand, const char * name )
     139                 : {
     140               2 :    this->poDS = pods;
     141               2 :    this->nBand = nband;
     142               2 :    this->poxmlBand = xmlBand;
     143               2 :    this->ChannelName = name;
     144               2 :    this->Aggregation = NULL;
     145               2 :    nBlockXSize = pods->nBlockXSize;
     146               2 :    nBlockYSize = pods->nBlockYSize;
     147                 : 
     148               2 :    switch(pods->reader->getChannel(name)->getDataType())
     149                 :    {
     150                 : #define DO_CASE(ltdt, gdt) case (ltdt): eDataType = gdt; break;
     151               2 :       DO_CASE(DATATYPE_FLOAT64, GDT_Float64);
     152               0 :       DO_CASE(DATATYPE_FLOAT32, GDT_Float32);
     153               0 :       DO_CASE(DATATYPE_SINT32, GDT_Int32);
     154               0 :       DO_CASE(DATATYPE_UINT32, GDT_UInt32);
     155               0 :       DO_CASE(DATATYPE_SINT16, GDT_Int16);
     156               0 :       DO_CASE(DATATYPE_UINT16, GDT_UInt16);
     157               0 :       DO_CASE(DATATYPE_SINT8, GDT_Byte);
     158               0 :       DO_CASE(DATATYPE_UINT8, GDT_Byte);
     159                 : default:
     160                 :    CPLError(CE_Failure, CPLE_AssertionFailed,
     161               0 :       "Invalid datatype in MG4 file");
     162                 :    break;
     163                 : #undef DO_CASE      
     164                 :    }
     165                 :    // Coerce datatypes as required.
     166               2 :    const char * ForceDataType =  CPLGetXMLValue(pods->poXMLPCView, "Datatype", NULL);
     167                 : 
     168               2 :    if (ForceDataType != NULL)
     169                 :    {
     170               2 :       GDALDataType dt = GDALGetDataTypeByName(ForceDataType);
     171               2 :       if (dt != GDT_Unknown)
     172               2 :          eDataType = dt;
     173                 :    }
     174                 : 
     175               2 :    CPLXMLNode *poxmlFilter = CPLGetXMLNode(poxmlBand, "ClassificationFilter");
     176               2 :    if( poxmlFilter == NULL )
     177               0 :       poxmlFilter = CPLGetXMLNode(pods->poXMLPCView, "ClassificationFilter");
     178               2 :    if (poxmlFilter == NULL || poxmlFilter->psChild == NULL ||
     179                 :        poxmlFilter->psChild->pszValue == NULL)
     180               0 :       papszFilterClassCodes = NULL;
     181                 :    else
     182               2 :       papszFilterClassCodes = CSLTokenizeString(poxmlFilter->psChild->pszValue);
     183                 : 
     184               2 :    poxmlFilter = CPLGetXMLNode(poxmlBand, "ReturnNumberFilter");
     185               2 :    if( poxmlFilter == NULL )
     186               2 :       poxmlFilter = CPLGetXMLNode(pods->poXMLPCView, "ReturnNumberFilter");
     187               4 :    if (poxmlFilter == NULL || poxmlFilter->psChild == NULL ||
     188                 :        poxmlFilter->psChild->pszValue == NULL)
     189               2 :       papszFilterReturnNums = NULL;
     190                 :    else
     191               0 :       papszFilterReturnNums = CSLTokenizeString(poxmlFilter->psChild->pszValue);
     192                 : 
     193                 :    
     194               2 :    CPLXMLNode * poxmlAggregation = CPLGetXMLNode(poxmlBand, "AggregationMethod");
     195               2 :    if( poxmlAggregation == NULL )
     196               2 :       poxmlAggregation = CPLGetXMLNode(pods->poXMLPCView, "AggregationMethod");
     197               4 :    if (poxmlAggregation == NULL || poxmlAggregation->psChild == NULL ||
     198                 :        poxmlAggregation->psChild->pszValue == NULL)
     199               2 :       Aggregation = "Mean";
     200                 :    else
     201               0 :       Aggregation = poxmlAggregation->psChild->pszValue;
     202                 : 
     203               2 :    nodatavalue = getMaxValue();
     204                 : 
     205               2 :    CPLXMLNode * poxmlIntepolation = CPLGetXMLNode(poxmlBand, "InterpolationMethod");
     206               2 :    if( poxmlIntepolation == NULL )
     207               2 :       poxmlIntepolation = CPLGetXMLNode(pods->poXMLPCView, "InterpolationMethod");
     208               2 :    if (poxmlIntepolation != NULL )
     209                 :    {
     210               0 :       CPLXMLNode * poxmlMethod= NULL;
     211               0 :       char ** papszParams = NULL;
     212               0 :       if (((poxmlMethod = CPLSearchXMLNode(poxmlIntepolation, "None")) != NULL) &&
     213                 :           poxmlMethod->psChild != NULL && poxmlMethod->psChild->pszValue != NULL)
     214                 :       {
     215               0 :          papszParams = CSLTokenizeString(poxmlMethod->psChild->pszValue);
     216               0 :          if (!EQUAL(papszParams[0], "MAX"))
     217               0 :             nodatavalue = atof(papszParams[0]);
     218                 :       }
     219                 :       // else if .... Add support for other interpolation methods here.
     220               0 :       CSLDestroy(papszParams);
     221                 :    }
     222               2 :    const char * filter = NULL;
     223               2 :    if (papszFilterClassCodes != NULL && papszFilterReturnNums != NULL)
     224               0 :       filter ="Classification and Return";
     225               2 :    if (papszFilterClassCodes != NULL)
     226               2 :       filter = "Classification";
     227               0 :    else if (papszFilterReturnNums != NULL)
     228               0 :       filter = "Return";
     229               2 :    CPLString osDesc;
     230               2 :    if (filter)
     231               2 :       osDesc.Printf("%s of %s (filtered by %s)", Aggregation, ChannelName.c_str(), filter);
     232                 :    else
     233               0 :       osDesc.Printf("%s of %s", Aggregation, ChannelName.c_str());
     234               2 :    SetDescription(osDesc);
     235               2 : }
     236                 : 
     237                 : /************************************************************************/
     238                 : /*                          ~MG4LidarRasterBand()                            */
     239                 : /************************************************************************/
     240               2 : MG4LidarRasterBand::~MG4LidarRasterBand()
     241                 : {
     242               2 :    CSLDestroy(papszFilterClassCodes);
     243               2 :    CSLDestroy(papszFilterReturnNums);
     244               2 : }
     245                 : 
     246                 : /************************************************************************/
     247                 : /*                          GetOverviewCount()                          */
     248                 : /************************************************************************/
     249                 : 
     250               0 : int MG4LidarRasterBand::GetOverviewCount()
     251                 : {
     252               0 :    MG4LidarDataset *poGDS = (MG4LidarDataset *) poDS;
     253               0 :    return poGDS->nOverviewCount;
     254                 : }
     255                 : 
     256                 : /************************************************************************/
     257                 : /*                            GetOverview()                             */
     258                 : /************************************************************************/
     259               1 : GDALRasterBand *MG4LidarRasterBand::GetOverview( int i )
     260                 : {
     261               1 :    MG4LidarDataset *poGDS = (MG4LidarDataset *) poDS;
     262                 : 
     263               1 :    if( i < 0 || i >= poGDS->nOverviewCount )
     264               0 :       return NULL;
     265                 :    else
     266               1 :       return poGDS->papoOverviewDS[i]->GetRasterBand( nBand );
     267                 : }
     268                 : template<typename DTYPE>
     269          407623 : const DTYPE GetChannelElement(const ChannelData &channel, size_t idx)
     270                 : {
     271          407623 :    DTYPE retval = static_cast<DTYPE>(0);
     272          407623 :    switch (channel.getDataType())
     273                 :    {
     274                 :    case (DATATYPE_FLOAT64):
     275          112133 :       retval = static_cast<DTYPE>(static_cast<const double*>(channel.getData())[idx]);
     276          112133 :       break;
     277                 :    case (DATATYPE_FLOAT32):
     278               0 :       retval = static_cast<DTYPE>(static_cast<const float *>(channel.getData())[idx]);
     279               0 :       break;
     280                 :    case (DATATYPE_SINT32):
     281               0 :       retval = static_cast<DTYPE>(static_cast<const long *>(channel.getData())[idx]);
     282               0 :       break;
     283                 :    case (DATATYPE_UINT32):
     284               0 :       retval = static_cast<DTYPE>(static_cast<const unsigned long *>(channel.getData())[idx]);
     285               0 :       break;
     286                 :    case (DATATYPE_SINT16):
     287               0 :       retval = static_cast<DTYPE>(static_cast<const short *>(channel.getData())[idx]);
     288               0 :       break;
     289                 :    case (DATATYPE_UINT16):
     290               0 :       retval = static_cast<DTYPE>(static_cast<const unsigned short *>(channel.getData())[idx]);
     291               0 :       break;
     292                 :    case (DATATYPE_SINT8):
     293               0 :       retval = static_cast<DTYPE>(static_cast<const char *>(channel.getData())[idx]);
     294               0 :       break;
     295                 :    case (DATATYPE_UINT8):
     296          295490 :       retval = static_cast<DTYPE>(static_cast<const unsigned char *>(channel.getData())[idx]);
     297          295490 :       break;
     298                 :    case (DATATYPE_SINT64):
     299               0 :       retval = static_cast<DTYPE>(static_cast<const GIntBig *>(channel.getData())[idx]);
     300               0 :       break;
     301                 :    case (DATATYPE_UINT64):
     302               0 :       retval = static_cast<DTYPE>(static_cast<const GUIntBig *>(channel.getData())[idx]);
     303                 :       break;
     304                 :    default:
     305                 :       break;
     306                 :    }
     307          407623 :    return retval;
     308                 : }
     309                 : 
     310                 : 
     311          295490 : const bool MG4LidarRasterBand::ElementPassesFilter(const PointData &pointdata, size_t i)
     312                 : {
     313          295490 :    bool bClassificationOK = true;
     314          295490 :    bool bReturnNumOK = true;
     315                 : 
     316                 :    // Check if classification code is ok:  it was requested and it does match one of the requested codes
     317          295490 :    const int classcode = GetChannelElement<int>(*pointdata.getChannel(CHANNEL_NAME_ClassId), i);
     318                 :    char bufCode[16];
     319          295490 :    sprintf(bufCode, "%d", classcode);
     320                 :    bClassificationOK = (papszFilterClassCodes == NULL ? true :
     321          295490 :       (CSLFindString(papszFilterClassCodes,bufCode)!=-1));
     322                 : 
     323          295490 :    if (bClassificationOK)
     324                 :    {
     325                 :       // Check if return num is ok:  it was requested and it does match one of the requested return numbers
     326          112133 :       const long returnnum= static_cast<const unsigned char *>(pointdata.getChannel(CHANNEL_NAME_ReturnNum)->getData())[i];
     327          112133 :       sprintf(bufCode, "%d", (int)returnnum);
     328                 :       bReturnNumOK = (papszFilterReturnNums == NULL ? true :
     329          112133 :          (CSLFindString(papszFilterReturnNums, bufCode)!=-1));
     330          112133 :       if (!bReturnNumOK && CSLFindString(papszFilterReturnNums, "Last")!=-1)
     331                 :       {  // Didn't find an explicit match (e.g. return number "1") so we handle a request for "Last" returns
     332               0 :          const long numreturns= GetChannelElement<long>(*pointdata.getChannel(CHANNEL_NAME_NumReturns), i);
     333               0 :          bReturnNumOK = (returnnum == numreturns);
     334                 :       }
     335                 :    }
     336                 : 
     337          295490 :    return bReturnNumOK && bClassificationOK;
     338                 : 
     339                 : }
     340                 : 
     341                 : 
     342                 : template<typename DTYPE>
     343               2 : CPLErr   MG4LidarRasterBand::doReadBlock(int nBlockXOff, int nBlockYOff, void * pImage)
     344                 : {
     345               2 :    MG4LidarDataset * poGDS = (MG4LidarDataset *)poDS;
     346               2 :    MG4PointReader *reader =poGDS->reader;
     347                 : 
     348                 :    struct Accumulator_t
     349                 :    {
     350                 :       DTYPE value;
     351                 :       int count;
     352                 :    } ;
     353               2 :    Accumulator_t * Accumulator = NULL;
     354               2 :    if (EQUAL(Aggregation, "Mean"))
     355                 :    {
     356               2 :       Accumulator = new Accumulator_t[nBlockXSize*nBlockYSize];
     357               2 :       memset (Accumulator, 0, sizeof(Accumulator_t)*nBlockXSize*nBlockYSize);
     358                 :    }
     359             688 :    for (int i = 0; i < nBlockXSize; i++)
     360                 :    {
     361          247117 :       for (int j = 0; j < nBlockYSize; j++)
     362                 :       {
     363          246431 :          static_cast<DTYPE*>(pImage)[i*nBlockYSize+j] = static_cast<DTYPE>(nodatavalue);
     364                 :       }
     365                 :    }
     366                 : 
     367                 :    double geoTrans[6];
     368               2 :    poGDS->GetGeoTransform(geoTrans);   
     369               2 :    double xres = geoTrans[1];
     370               2 :    double yres = geoTrans[5];
     371                 : 
     372                 :    // Get the extent of the requested block.
     373               2 :    double xmin = geoTrans[0] + (nBlockXOff *nBlockXSize* xres);
     374               2 :    double xmax = xmin + nBlockXSize* xres;
     375               2 :    double ymax = reader->getBounds().y.max - (nBlockYOff * nBlockYSize* -yres);
     376               2 :    double ymin = ymax - nBlockYSize* -yres;
     377               2 :    Bounds bounds(xmin, xmax,  ymin, ymax, -HUGE_VAL, +HUGE_VAL); 
     378               2 :    PointData pointdata;
     379               2 :    pointdata.init(reader->getPointInfo(), 4096);
     380               2 :    double fraction = 1.0/pow(RESOLUTION_RATIO, poGDS->iLevel);
     381               2 :    CPLDebug( "MG4Lidar", "IReadBlock(x=%d y=%d, level=%d, fraction=%f)", nBlockXOff, nBlockYOff, poGDS->iLevel, fraction);
     382               2 :    Scoped<PointIterator> iter(reader->createIterator(bounds, fraction, reader->getPointInfo(), NULL));
     383                 : 
     384               2 :    const double * x = pointdata.getX();
     385               2 :    const double * y = pointdata.getY();
     386                 :    size_t nPoints;
     387              78 :    while ( (nPoints = iter->getNextPoints(pointdata)) != 0)
     388                 :    {
     389          295564 :       for( size_t i = 0; i < nPoints; i++ )
     390                 :       {
     391          295490 :          const ChannelData * channel = pointdata.getChannel(ChannelName);
     392          295490 :          if (papszFilterClassCodes || papszFilterReturnNums)
     393                 :          {
     394          295490 :             if (!ElementPassesFilter(pointdata, i))
     395          183357 :                continue;
     396                 :          }
     397          112133 :          double col = (x[i] - xmin) / xres;
     398          112133 :          double row = (ymax - y[i]) / -yres;
     399          112133 :          col = floor (col);
     400          112133 :          row = floor (row);
     401                 : 
     402          112133 :          if (row < 0) 
     403               0 :             row = 0;
     404          112133 :          else if (row >= nBlockYSize) 
     405               0 :             row = nBlockYSize - 1;
     406          112133 :          if (col < 0) 
     407               0 :             col = 0;
     408          112133 :          else if (col >= nBlockXSize ) 
     409               1 :             col = nBlockXSize - 1;
     410                 : 
     411          112133 :          int iCol = (int) (col);
     412          112133 :          int iRow = (int) (row);
     413          112133 :          const int offset =iRow* nBlockXSize + iCol;
     414          112133 :          if (EQUAL(Aggregation, "Max"))
     415                 :          {
     416               0 :             DTYPE value = GetChannelElement<DTYPE>(*channel, i);
     417               0 :             if (static_cast<DTYPE *>(pImage)[offset] == static_cast<DTYPE>(nodatavalue) ||
     418                 :                static_cast<DTYPE *>(pImage)[offset] < value)
     419               0 :                static_cast<DTYPE *>(pImage)[offset] = value;
     420                 :          }
     421          112133 :          else if (EQUAL(Aggregation, "Min"))
     422                 :          {
     423               0 :             DTYPE value = GetChannelElement<DTYPE>(*channel, i);
     424               0 :             if (static_cast<DTYPE *>(pImage)[offset] == static_cast<DTYPE>(nodatavalue) ||
     425                 :                static_cast<DTYPE *>(pImage)[offset] > value)
     426               0 :                static_cast<DTYPE *>(pImage)[offset] = value;
     427                 :          }
     428          112133 :          else if (EQUAL(Aggregation, "Mean"))
     429                 :          {
     430          112133 :             DTYPE value = GetChannelElement<DTYPE>(*channel, i);
     431          112133 :             Accumulator[offset].count++;
     432          112133 :             Accumulator[offset].value += value;
     433          112133 :             static_cast<DTYPE*>(pImage)[offset] = static_cast<DTYPE>(Accumulator[offset].value / Accumulator[offset].count);
     434                 :          }
     435                 :       }
     436                 :    }
     437                 :    
     438               2 :    delete[] Accumulator;
     439               2 :    return CE_None;
     440                 : }
     441                 : 
     442               2 : double MG4LidarRasterBand::getMaxValue()
     443                 : {
     444                 :    double retval;
     445               2 :    switch(eDataType)
     446                 :    {
     447                 :       #define DO_CASE(gdt, largestvalue)  case (gdt):\
     448                 :          retval = static_cast<double>(largestvalue); \
     449                 :          break;
     450                 :          
     451               0 :       DO_CASE (GDT_Float64, DBL_MAX);
     452               2 :       DO_CASE (GDT_Float32, FLT_MAX);
     453               0 :       DO_CASE (GDT_Int32, INT_MAX);
     454               0 :       DO_CASE (GDT_UInt32, UINT_MAX);
     455               0 :       DO_CASE (GDT_Int16, SHRT_MAX);
     456               0 :       DO_CASE (GDT_UInt16, USHRT_MAX);
     457               0 :       DO_CASE (GDT_Byte, UCHAR_MAX);
     458                 :       #undef DO_CASE 
     459                 :        default:
     460               0 :            retval = 0;
     461                 :            break;
     462                 :    }
     463               2 :    return retval;
     464                 : }
     465                 : /************************************************************************/
     466                 : /*                             IReadBlock()                             */
     467                 : /************************************************************************/
     468                 : 
     469               2 : CPLErr MG4LidarRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
     470                 :                                       void * pImage )
     471                 : {
     472                 :    CPLErr eErr;
     473               2 :    switch(eDataType)
     474                 :    {
     475                 : #define DO_CASE(gdt, nativedt)  case (gdt):\
     476                 :    eErr = doReadBlock<nativedt>(nBlockXOff, nBlockYOff, pImage); \
     477                 :    break;
     478                 : 
     479               0 :       DO_CASE (GDT_Float64, double);
     480               2 :       DO_CASE (GDT_Float32, float);
     481               0 :       DO_CASE (GDT_Int32, long);
     482               0 :       DO_CASE (GDT_UInt32, unsigned long);
     483               0 :       DO_CASE (GDT_Int16, short);
     484               0 :       DO_CASE (GDT_UInt16, unsigned short);
     485               0 :       DO_CASE (GDT_Byte, char);
     486                 : #undef DO_CASE 
     487                 :       default:
     488               0 :            return CE_Failure;
     489                 :            break;
     490                 : 
     491                 :    }
     492               2 :    return CE_None;
     493                 : }
     494                 : 
     495                 : /************************************************************************/
     496                 : /*                           GetStatistics()                            */
     497                 : /************************************************************************/
     498                 : 
     499               0 : CPLErr MG4LidarRasterBand::GetStatistics( int bApproxOK, int bForce,
     500                 :                                          double *pdfMin, double *pdfMax, 
     501                 :                                          double *pdfMean, double *pdfStdDev )
     502                 : 
     503                 : {
     504               0 :    bApproxOK = TRUE; 
     505               0 :    bForce = TRUE;
     506                 : 
     507                 :    return GDALPamRasterBand::GetStatistics( bApproxOK, bForce, 
     508                 :       pdfMin, pdfMax, 
     509               0 :       pdfMean, pdfStdDev );   
     510                 : 
     511                 : }
     512                 : /************************************************************************/
     513                 : /*                           GetNoDataValue()                           */
     514                 : /************************************************************************/
     515                 : 
     516               0 : double MG4LidarRasterBand::GetNoDataValue( int *pbSuccess )
     517                 : {
     518               0 :    if (pbSuccess)
     519               0 :        *pbSuccess = TRUE;
     520               0 :    return nodatavalue;
     521                 : }
     522                 : 
     523                 : 
     524                 : /************************************************************************/
     525                 : /*                            MG4LidarDataset()                             */
     526                 : /************************************************************************/
     527               2 : MG4LidarDataset::MG4LidarDataset()
     528                 : {
     529               2 :    reader = NULL;
     530               2 :    fileIO = NULL;
     531                 : 
     532               2 :    poXMLPCView = NULL;
     533               2 :    ownsXML = false;
     534               2 :    nOverviewCount = 0;
     535               2 :    papoOverviewDS = NULL;
     536                 : 
     537               2 : }
     538                 : /************************************************************************/
     539                 : /*                            ~MG4LidarDataset()                             */
     540                 : /************************************************************************/
     541                 : 
     542               2 : MG4LidarDataset::~MG4LidarDataset()
     543                 : 
     544                 : {
     545               2 :    FlushCache();
     546               2 :    if ( papoOverviewDS )
     547                 :    {
     548               2 :       for( int i = 0; i < nOverviewCount; i++ )
     549               1 :          delete papoOverviewDS[i];
     550               1 :       CPLFree( papoOverviewDS );
     551                 :    }
     552               2 :    if (ownsXML)
     553               1 :       CPLDestroyXMLNode(poXMLPCView);
     554                 : 
     555               2 :    RELEASE(reader);
     556               2 :    RELEASE(fileIO);
     557               2 : }
     558                 : 
     559                 : /************************************************************************/
     560                 : /*                          GetGeoTransform()                           */
     561                 : /************************************************************************/
     562                 : 
     563               3 : CPLErr MG4LidarDataset::GetGeoTransform( double * padfTransform )
     564                 : 
     565                 : {
     566               3 :    padfTransform[0] = reader->getBounds().x.min ;// Upper left X, Y
     567               3 :    padfTransform[3] = reader->getBounds().y.max; // 
     568               3 :    padfTransform[1] = reader->getBounds().x.length()/GetRasterXSize(); //xRes
     569               3 :    padfTransform[2] = 0.0;
     570                 : 
     571               3 :    padfTransform[4] = 0.0;
     572               3 :    padfTransform[5] = -1 * reader->getBounds().y.length()/GetRasterYSize(); //yRes
     573                 : 
     574               3 :    return CE_None;
     575                 : }
     576                 : 
     577                 : /************************************************************************/
     578                 : /*                          GetProjectionRef()                          */
     579                 : /************************************************************************/
     580                 : 
     581               1 : const char *MG4LidarDataset::GetProjectionRef()
     582                 : 
     583                 : {
     584               1 :    const char * wkt = CPLGetXMLValue(poXMLPCView, "GeoReference", NULL);
     585               1 :    if (wkt == NULL)
     586               1 :       wkt = reader->getWKT(); 
     587               1 :    return(wkt);
     588                 : }
     589                 : 
     590                 : /************************************************************************/
     591                 : /*                             OpenZoomLevel()                          */
     592                 : /************************************************************************/
     593                 : 
     594               2 : CPLErr MG4LidarDataset::OpenZoomLevel( int iZoom )
     595                 : {
     596                 :    /* -------------------------------------------------------------------- */
     597                 :    /*      Get image geometry.                                            */
     598                 :    /* -------------------------------------------------------------------- */
     599               2 :    iLevel = iZoom;
     600                 : 
     601                 :    // geo dimensions
     602               2 :    const double gWidth = reader->getBounds().x.length() ;
     603               2 :    const double gHeight = reader->getBounds().y.length() ;
     604                 : 
     605                 :    // geo res
     606               2 :    double xRes = pow(RESOLUTION_RATIO, iZoom) * gWidth / MaxRasterSize ;
     607               2 :    double yRes = pow(RESOLUTION_RATIO, iZoom) * gHeight / MaxRasterSize ;
     608               2 :    xRes = yRes = MAX(xRes, yRes);
     609                 : 
     610                 :    // pixel dimensions
     611               2 :    nRasterXSize  = static_cast<int>(gWidth / xRes  + 0.5);
     612               2 :    nRasterYSize  = static_cast<int>(gHeight / yRes + 0.5);
     613                 : 
     614               2 :    nBlockXSize = static_cast<int>(MIN(MaxBlockSideSize , GetRasterXSize()));
     615               2 :    nBlockYSize = static_cast<int>(MIN(MaxBlockSideSize , GetRasterYSize())); 
     616                 : 
     617                 :    CPLDebug( "MG4Lidar", "Opened zoom level %d with size %dx%d.\n",
     618               2 :       iZoom, nRasterXSize, nRasterYSize );
     619                 : 
     620                 : 
     621                 : 
     622                 :    /* -------------------------------------------------------------------- */
     623                 :    /*  Handle sample type and color space.                                 */
     624                 :    /* -------------------------------------------------------------------- */
     625                 :    //eColorSpace = poImageReader->getColorSpace();
     626                 :    /* -------------------------------------------------------------------- */
     627                 :    /*      Create band information objects.                                */
     628                 :    /* -------------------------------------------------------------------- */
     629               2 :    size_t BandCount = 0;
     630               2 :    CPLXMLNode* xmlBand = poXMLPCView;
     631               2 :    bool bClass = false;
     632               2 :    bool bNumRets = false;
     633               2 :    bool bRetNum = false;
     634               6 :    while ((xmlBand = CPLSearchXMLNode(xmlBand, "Band")) != NULL)
     635                 :    {
     636               2 :       CPLXMLNode * xmlChannel = CPLSearchXMLNode(xmlBand, "Channel");
     637               2 :       const char * name = "Z";
     638               2 :       if (xmlChannel && xmlChannel->psChild && xmlChannel->psChild->pszValue)
     639               2 :          name = xmlChannel->psChild->pszValue;
     640                 :       
     641               2 :       BandCount++;
     642               2 :       MG4LidarRasterBand *band = new MG4LidarRasterBand(this, BandCount, xmlBand, name);
     643               2 :       SetBand(BandCount, band);
     644               4 :       if (band->papszFilterClassCodes) bClass = true;
     645               2 :       if (band->papszFilterReturnNums) bNumRets = true;
     646               2 :       if (bNumRets && CSLFindString(band->papszFilterReturnNums, "Last")) bRetNum = true;
     647               2 :       xmlBand = xmlBand->psNext;
     648                 :    }
     649               2 :    nBands = BandCount;
     650               2 :    int nSDKChannels = BandCount + (bClass ? 1 : 0) + (bNumRets ? 1 : 0) + (bRetNum ? 1 : 0);
     651               2 :    if (BandCount == 0)  // default if no bands specified.
     652                 :    {
     653               0 :       MG4LidarRasterBand *band = new MG4LidarRasterBand(this, 1, NULL, CHANNEL_NAME_Z);
     654               0 :       SetBand(1, band);
     655               0 :       nBands = 1;
     656               0 :       nSDKChannels = 1;
     657                 :    }
     658               2 :    requiredChannels.init(nSDKChannels);
     659               2 :    const ChannelInfo *ci = NULL;
     660               4 :    for (int i=0; i<nBands; i++)
     661                 :    {
     662               2 :       ci = reader->getChannel(dynamic_cast<MG4LidarRasterBand*>(papoBands[i])->ChannelName);
     663               2 :       requiredChannels.getChannel(i).init(*ci);
     664                 :    }
     665               2 :    int iSDKChannels = nBands;
     666               2 :    if (bClass)
     667                 :    {
     668               2 :       ci = reader->getChannel(CHANNEL_NAME_ClassId);
     669               2 :       requiredChannels.getChannel(iSDKChannels++).init(*ci);
     670                 :    }
     671               2 :    if (bRetNum)
     672                 :    {
     673               0 :       ci = reader->getChannel(CHANNEL_NAME_ReturnNum);
     674               0 :       requiredChannels.getChannel(iSDKChannels++).init(*ci);
     675                 :    }
     676               2 :    if (bNumRets)
     677                 :    {
     678               0 :       ci = reader->getChannel(CHANNEL_NAME_NumReturns);
     679               0 :       requiredChannels.getChannel(iSDKChannels++).init(*ci);
     680                 :    }
     681                 : 
     682               2 :    return CE_None;
     683                 : }
     684                 : 
     685                 : /************************************************************************/
     686                 : /*                                Open()                                */
     687                 : /************************************************************************/
     688                 : 
     689           12617 : GDALDataset *MG4LidarDataset::Open( GDALOpenInfo * poOpenInfo )
     690                 : 
     691                 : {
     692                 : #ifdef notdef
     693                 :    CPLPushErrorHandler( CPLLoggingErrorHandler );
     694                 :    CPLSetConfigOption( "CPL_DEBUG", "ON" );
     695                 :    CPLSetConfigOption( "CPL_LOG", "C:\\ArcGIS_GDAL\\jdem\\cpl.log" );
     696                 : #endif
     697                 : 
     698           12617 :    if( poOpenInfo->fp == NULL || poOpenInfo->nHeaderBytes < 32 )
     699           11682 :       return NULL;
     700                 : 
     701                 :    CPLXMLNode *pxmlPCView;
     702                 : 
     703                 :    // do something sensible for .sid files without a .view
     704             935 :    if( EQUALN((const char *) poOpenInfo->pabyHeader, "msid", 4) )
     705                 :    {
     706                 :       int gen;
     707                 :       bool raster;
     708               0 :       if( !Version::getMrSIDFileVersion(poOpenInfo->pabyHeader, gen, raster)
     709                 :           || raster )
     710               0 :          return NULL;
     711                 : 
     712               0 :       CPLString xmltmp( "<PointCloudView><InputFile>" );
     713               0 :       xmltmp.append( poOpenInfo->pszFilename );
     714               0 :       xmltmp.append( "</InputFile></PointCloudView>" );
     715               0 :       pxmlPCView = CPLParseXMLString( xmltmp );
     716               0 :       if (pxmlPCView == NULL)
     717               0 :          return NULL;
     718                 :    }
     719                 :    else
     720                 :    {
     721                 :       // support .view xml
     722             935 :       if( !EQUALN((const char *) poOpenInfo->pabyHeader, "<PointCloudView", 15 ) )
     723             934 :          return NULL;
     724                 : 
     725               1 :       pxmlPCView = CPLParseXMLFile( poOpenInfo->pszFilename );
     726               1 :       if (pxmlPCView == NULL)
     727               0 :           return NULL;
     728                 :    }
     729                 : 
     730               1 :    CPLXMLNode *psInputFile = CPLGetXMLNode( pxmlPCView, "InputFile" );
     731               1 :    if( psInputFile == NULL )
     732                 :    {
     733                 :       CPLError( CE_Failure, CPLE_OpenFailed, 
     734               0 :          "Failed to find <InputFile> in document." );
     735               0 :       CPLDestroyXMLNode(pxmlPCView);
     736               0 :       return NULL;
     737                 :    }
     738               1 :    CPLString sidInputName(psInputFile->psChild->pszValue);
     739               1 :    if (CPLIsFilenameRelative(sidInputName))
     740                 :    {
     741               1 :       CPLString dirname(CPLGetDirname(poOpenInfo->pszFilename));
     742               1 :       sidInputName = CPLString(CPLFormFilename(dirname, sidInputName, NULL));
     743                 :    }
     744               1 :    GDALOpenInfo openinfo(sidInputName, GA_ReadOnly);
     745                 : 
     746                 :    /* -------------------------------------------------------------------- */
     747                 :    /*      Check that particular fields in the header are valid looking    */
     748                 :    /*      dates.                                                          */
     749                 :    /* -------------------------------------------------------------------- */
     750               1 :    if( openinfo.fp == NULL || openinfo.nHeaderBytes < 50 )
     751                 :    {
     752               0 :       CPLDestroyXMLNode(pxmlPCView);
     753               0 :       return NULL;
     754                 :    }
     755                 : 
     756                 :    /* check magic */
     757                 :    // to do:  SDK should provide an API for this.
     758               1 :    if(  !EQUALN((const char *) openinfo.pabyHeader, "msid", 4)
     759                 :       || (*(openinfo.pabyHeader+4) != 0x4 )) // Generation 4.  ... is there more we can check?
     760                 :    {
     761               0 :       CPLDestroyXMLNode(pxmlPCView);
     762               0 :       return NULL;
     763                 :    }
     764                 : 
     765                 :    /* -------------------------------------------------------------------- */
     766                 :    /*      Create a corresponding GDALDataset.                             */
     767                 :    /* -------------------------------------------------------------------- */
     768                 :    MG4LidarDataset  *poDS;
     769                 : 
     770               1 :    poDS = new MG4LidarDataset();
     771               1 :    poDS->poXMLPCView = pxmlPCView;
     772               1 :    poDS->ownsXML = true;
     773               2 :    poDS->reader = CropableMG4PointReader::create();
     774               1 :    poDS->fileIO = FileIO::create();
     775                 : 
     776               1 :    const char * pszClipExtent = CPLGetXMLValue(pxmlPCView, "ClipBox", NULL);
     777               1 :    MG4PointReader *r = MG4PointReader::create();
     778               1 :    FileIO* io = FileIO::create();
     779                 : 
     780                 : #if (defined(WIN32) && _MSC_VER >= 1310) || __MSVCRT_VERSION__ >= 0x0601
     781                 :    bool bIsUTF8 = CSLTestBoolean( CPLGetConfigOption( "GDAL_FILENAME_IS_UTF8", "YES" ) );
     782                 :    wchar_t *pwszFilename = NULL;
     783                 :    if (bIsUTF8)
     784                 :    {
     785                 :       pwszFilename = CPLRecodeToWChar(openinfo.pszFilename, CPL_ENC_UTF8, CPL_ENC_UCS2);
     786                 :       if (!pwszFilename)
     787                 :       {
     788                 :          RELEASE(r);
     789                 :          RELEASE(io);
     790                 :          return NULL;
     791                 :        }
     792                 :        io->init(pwszFilename, "r");
     793                 :    }
     794                 :    else
     795                 :       io->init(openinfo.pszFilename, "r");
     796                 : #else
     797               1 :    io->init(openinfo.pszFilename, "r");
     798                 : #endif
     799               1 :    r->init(io);
     800               1 :    Bounds bounds = r->getBounds();
     801               1 :    if (pszClipExtent)
     802                 :    {
     803               0 :       char ** papszClipExtent = CSLTokenizeString(pszClipExtent);
     804               0 :       int cslcount = CSLCount(papszClipExtent);
     805               0 :       if (cslcount != 4 && cslcount != 6)
     806                 :       {
     807                 :          CPLError( CE_Failure, CPLE_OpenFailed, 
     808               0 :             "Invalid ClipBox.  Must contain 4 or 6 floats." );
     809               0 :          CSLDestroy(papszClipExtent);
     810               0 :          delete poDS;
     811               0 :          RELEASE(r);
     812               0 :          RELEASE(io);
     813                 : #if (defined(WIN32) && _MSC_VER >= 1310) || __MSVCRT_VERSION__ >= 0x0601
     814                 :          if ( pwszFilename ) 
     815                 :             CPLFree( pwszFilename );
     816                 : #endif
     817               0 :          return NULL;
     818                 :       }
     819               0 :       if (!EQUAL(papszClipExtent[0], "NOFILTER"))
     820               0 :          bounds.x.min = atof(papszClipExtent[0]);
     821               0 :       if (!EQUAL(papszClipExtent[1], "NOFILTER"))
     822               0 :          bounds.x.max = atof(papszClipExtent[1]);
     823               0 :       if (!EQUAL(papszClipExtent[2], "NOFILTER"))
     824               0 :          bounds.y.min = atof(papszClipExtent[2]);
     825               0 :       if (!EQUAL(papszClipExtent[3], "NOFILTER"))
     826               0 :          bounds.y.max = atof(papszClipExtent[3]);
     827               0 :       if (cslcount == 6)
     828                 :       {
     829               0 :          if (!EQUAL(papszClipExtent[4], "NOFILTER"))
     830               0 :             bounds.z.min = atof(papszClipExtent[4]);
     831               0 :          if (!EQUAL(papszClipExtent[5], "NOFILTER"))
     832               0 :             bounds.z.max = atof(papszClipExtent[5]);
     833                 :       }
     834               0 :       CSLDestroy(papszClipExtent);
     835                 :    }
     836                 : 
     837                 : #if (defined(WIN32) && _MSC_VER >= 1310) || __MSVCRT_VERSION__ >= 0x0601
     838                 :    if (bIsUTF8)
     839                 :    {
     840                 :       poDS->fileIO->init(pwszFilename, "r");
     841                 :       CPLFree(pwszFilename);
     842                 :    }
     843                 :    else
     844                 :       poDS->fileIO->init( openinfo.pszFilename, "r" );
     845                 : #else
     846               1 :    poDS->fileIO->init( openinfo.pszFilename, "r" );
     847                 : #endif
     848               1 :    dynamic_cast<CropableMG4PointReader *>(poDS->reader)->init(poDS->fileIO, &bounds);
     849               1 :    poDS->SetDescription(poOpenInfo->pszFilename);
     850               1 :    poDS->TryLoadXML(); 
     851                 : 
     852               1 :    double pts_per_area = ((double)r->getNumPoints())/(r->getBounds().x.length()*r->getBounds().y.length());
     853               1 :    double average_pt_spacing = sqrt(1.0 / pts_per_area) ;
     854               1 :    double cell_side = average_pt_spacing;
     855               1 :    const char * pszCellSize = CPLGetXMLValue(pxmlPCView, "CellSize", NULL);
     856               1 :    if (pszCellSize)
     857               0 :       cell_side = atof(pszCellSize);
     858               1 :    MaxRasterSize = MAX(poDS->reader->getBounds().x.length()/cell_side, poDS->reader->getBounds().y.length()/cell_side);
     859                 : 
     860               1 :    RELEASE(r);
     861               1 :    RELEASE(io);
     862                 : 
     863                 :    // Calculate the number of levels to expose.  The highest level correpsonds to a
     864                 :    // raster size of 256 on the longest side.
     865               1 :    double blocksizefactor = MaxRasterSize/256.0;
     866               1 :    poDS->nOverviewCount = MAX(0, (int)(log(blocksizefactor)/log(RESOLUTION_RATIO) + 0.5));
     867               1 :    if ( poDS->nOverviewCount > 0 )
     868                 :    {
     869                 :       int i;
     870                 : 
     871                 :       poDS->papoOverviewDS = (MG4LidarDataset **)
     872               1 :          CPLMalloc( poDS->nOverviewCount * (sizeof(void*)) );
     873                 : 
     874               2 :       for ( i = 0; i < poDS->nOverviewCount; i++ )
     875                 :       {
     876               1 :          poDS->papoOverviewDS[i] = new MG4LidarDataset ();
     877               2 :          poDS->papoOverviewDS[i]->reader = RETAIN(poDS->reader);
     878               1 :          poDS->papoOverviewDS[i]->SetMetadata(poDS->GetMetadata("MG4Lidar"), "MG4Lidar");
     879               1 :          poDS->papoOverviewDS[i]->poXMLPCView = pxmlPCView;
     880               1 :          poDS->papoOverviewDS[i]->OpenZoomLevel( i+1 );
     881                 :       }       
     882                 :    }
     883                 : 
     884                 :    /* -------------------------------------------------------------------- */
     885                 :    /*      Create object for the whole image.                              */
     886                 :    /* -------------------------------------------------------------------- */
     887               1 :    poDS->OpenZoomLevel( 0 );
     888                 : 
     889                 :    CPLDebug( "MG4Lidar",
     890                 :       "Opened image: width %d, height %d, bands %d",
     891               1 :       poDS->nRasterXSize, poDS->nRasterYSize, poDS->nBands );
     892                 : 
     893               1 :    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     894                 :    {
     895               0 :        delete poDS;
     896               0 :        return NULL;
     897                 :    }
     898                 : 
     899               1 :    if (! ((poDS->nBands == 1) || (poDS->nBands == 3)))
     900                 :    {
     901                 :       CPLDebug( "MG4Lidar",
     902               0 :          "Inappropriate number of bands (%d)", poDS->nBands );
     903               0 :       delete poDS;
     904               0 :       return(NULL);
     905                 :    }
     906                 : 
     907               1 :    return( poDS );
     908                 : }
     909                 : 
     910                 : /************************************************************************/
     911                 : /*                          GDALRegister_MG4Lidar()                        */
     912                 : /************************************************************************/
     913                 : 
     914             582 : void GDALRegister_MG4Lidar()
     915                 : 
     916                 : {
     917                 :    GDALDriver *poDriver;
     918                 : 
     919             582 :     if (! GDAL_CHECK_VERSION("MG4Lidar driver"))
     920               0 :         return;
     921                 : 
     922             582 :    if( GDALGetDriverByName( "MG4Lidar" ) == NULL )
     923                 :    {
     924             561 :       poDriver = new GDALDriver();
     925                 : 
     926             561 :       poDriver->SetDescription( "MG4Lidar" );
     927                 :       poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 
     928             561 :          "MrSID Generation 4 / Lidar (.sid)" );
     929                 :       // To do:  update this help file in gdal.org
     930                 :       poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 
     931             561 :          "frmt_mrsid_lidar.html" );
     932                 : 
     933             561 :       poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "view" );
     934                 :       poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, 
     935             561 :          "Float64" );
     936                 : 
     937             561 :       poDriver->pfnOpen = MG4LidarDataset::Open;
     938                 : 
     939             561 :       GetGDALDriverManager()->RegisterDriver( poDriver );
     940                 :    }
     941                 : }

Generated by: LCOV version 1.7