EVOLUTION-MANAGER
Edit File: sgidataset.cpp
/****************************************************************************** * $Id: sgidataset.cpp 27729 2014-09-24 00:40:16Z goatbar $ * * Project: SGI Image Driver * Purpose: Implement SGI Image Support based on Paul Bourke's SGI Image code. * http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/ * ftp://ftp.sgi.com/graphics/SGIIMAGESPEC * Authors: Mike Mazzella (GDAL driver) * Paul Bourke (original SGI format code) * Frank Warmerdam (write support) * ****************************************************************************** * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com> * Copyright (c) 2008-2010, 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 "cpl_port.h" #include "cpl_string.h" CPL_CVSID("$Id: sgidataset.cpp 27729 2014-09-24 00:40:16Z goatbar $"); CPL_C_START void GDALRegister_SGI(void); CPL_C_END struct ImageRec { GUInt16 imagic; GByte type; GByte bpc; GUInt16 dim; GUInt16 xsize; GUInt16 ysize; GUInt16 zsize; GUInt32 min; GUInt32 max; char wasteBytes[4]; char name[80]; GUInt32 colorMap; VSILFILE* file; std::string fileName; unsigned char* tmp; GUInt32 rleEnd; int rleTableDirty; GUInt32* rowStart; GInt32* rowSize; ImageRec() : imagic(0), type(0), bpc(1), dim(0), xsize(0), ysize(0), zsize(0), min(0), max(0), colorMap(0), file(NULL), fileName(""), tmp(NULL), rleEnd(0), rleTableDirty(FALSE), rowStart(NULL), rowSize(NULL) { memset(wasteBytes, 0, 4); memset(name, 0, 80); } void Swap() { #ifdef CPL_LSB CPL_SWAP16PTR(&imagic); CPL_SWAP16PTR(&dim); CPL_SWAP16PTR(&xsize); CPL_SWAP16PTR(&ysize); CPL_SWAP16PTR(&zsize); CPL_SWAP32PTR(&min); CPL_SWAP32PTR(&max); #endif } }; /************************************************************************/ /* ConvertLong() */ /************************************************************************/ static void ConvertLong(GUInt32* array, GInt32 length) { #ifdef CPL_LSB GUInt32* ptr; ptr = (GUInt32*)array; while(length--) CPL_SWAP32PTR(ptr++); #endif } /************************************************************************/ /* ImageGetRow() */ /************************************************************************/ static CPLErr ImageGetRow(ImageRec* image, unsigned char* buf, int y, int z) { unsigned char *iPtr, *oPtr, pixel; int count; y = image->ysize - 1 - y; if(int(image->type) == 1) { // reads row VSIFSeekL(image->file, (long)image->rowStart[y+z*image->ysize], SEEK_SET); if(VSIFReadL(image->tmp, 1, (GUInt32)image->rowSize[y+z*image->ysize], image->file) != (GUInt32)image->rowSize[y+z*image->ysize]) { CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str()); return CE_Failure; } // expands row iPtr = image->tmp; oPtr = buf; int xsizeCount = 0; for(;;) { pixel = *iPtr++; count = (int)(pixel & 0x7F); if(!count) { if(xsizeCount != image->xsize) { CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str()); return CE_Failure; } else { return CE_None; } } if( xsizeCount + count > image->xsize ) { CPLError(CE_Failure, CPLE_AppDefined, "Wrong repetition number that would overflow data at line %d", y); return CE_Failure; } if(pixel & 0x80) { memcpy(oPtr, iPtr, count); iPtr += count; } else { pixel = *iPtr++; memset(oPtr, pixel, count); } oPtr += count; xsizeCount += count; } } else { VSIFSeekL(image->file, 512+(y*image->xsize)+(z*image->xsize*image->ysize), SEEK_SET); if(VSIFReadL(buf, 1, image->xsize, image->file) != image->xsize) { CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str()); return CE_Failure; } } return CE_None; } /************************************************************************/ /* ==================================================================== */ /* SGIDataset */ /* ==================================================================== */ /************************************************************************/ class SGIRasterBand; class SGIDataset : public GDALPamDataset { friend class SGIRasterBand; VSILFILE* fpImage; int bGeoTransformValid; double adfGeoTransform[6]; ImageRec image; public: SGIDataset(); ~SGIDataset(); virtual CPLErr GetGeoTransform(double*); static GDALDataset* Open(GDALOpenInfo*); static GDALDataset *Create( const char * pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eType, char **papszOptions ); }; /************************************************************************/ /* ==================================================================== */ /* SGIRasterBand */ /* ==================================================================== */ /************************************************************************/ class SGIRasterBand : public GDALPamRasterBand { friend class SGIDataset; public: SGIRasterBand(SGIDataset*, int); virtual CPLErr IReadBlock(int, int, void*); virtual CPLErr IWriteBlock(int, int, void*); virtual GDALColorInterp GetColorInterpretation(); }; /************************************************************************/ /* SGIRasterBand() */ /************************************************************************/ SGIRasterBand::SGIRasterBand(SGIDataset* poDS, int nBand) { this->poDS = poDS; this->nBand = nBand; if(poDS == NULL) { eDataType = GDT_Byte; } else { if(int(poDS->image.bpc) == 1) eDataType = GDT_Byte; else eDataType = GDT_Int16; } nBlockXSize = poDS->nRasterXSize;; nBlockYSize = 1; } /************************************************************************/ /* IReadBlock() */ /************************************************************************/ CPLErr SGIRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, void* pImage) { SGIDataset* poGDS = (SGIDataset*) poDS; CPLAssert(nBlockXOff == 0); /* -------------------------------------------------------------------- */ /* Load the desired data into the working buffer. */ /* -------------------------------------------------------------------- */ return ImageGetRow(&(poGDS->image), (unsigned char*)pImage, nBlockYOff, nBand-1); } /************************************************************************/ /* IWritelock() */ /************************************************************************/ CPLErr SGIRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, void* pImage) { SGIDataset* poGDS = (SGIDataset*) poDS; ImageRec *image = &(poGDS->image); CPLAssert(nBlockXOff == 0); /* -------------------------------------------------------------------- */ /* Handle the fairly trivial non-RLE case. */ /* -------------------------------------------------------------------- */ if( image->type == 0 ) { VSIFSeekL(image->file, 512 + (nBlockYOff*image->xsize) + ((nBand-1)*image->xsize*image->ysize ), SEEK_SET); if(VSIFWriteL(pImage, 1, image->xsize, image->file) != image->xsize) { CPLError(CE_Failure, CPLE_OpenFailed, "file write error: row (%d)\n", nBlockYOff ); return CE_Failure; } return CE_None; } /* -------------------------------------------------------------------- */ /* Handle RLE case. */ /* -------------------------------------------------------------------- */ const GByte *pabyRawBuf = (const GByte *) pImage; GByte *pabyRLEBuf = (GByte *) CPLMalloc(image->xsize * 2 + 6); int iX = 0, nRLEBytes = 0; while( iX < image->xsize ) { int nRepeatCount = 1; while( iX + nRepeatCount < image->xsize && nRepeatCount < 127 && pabyRawBuf[iX + nRepeatCount] == pabyRawBuf[iX] ) nRepeatCount++; if( nRepeatCount > 2 || iX + nRepeatCount == image->xsize || (iX + nRepeatCount < image->xsize - 2 && pabyRawBuf[iX + nRepeatCount + 1] == pabyRawBuf[iX + nRepeatCount + 2] && pabyRawBuf[iX + nRepeatCount + 1] == pabyRawBuf[iX + nRepeatCount + 3]) ) { // encode a constant run. pabyRLEBuf[nRLEBytes++] = (GByte) nRepeatCount; pabyRLEBuf[nRLEBytes++] = pabyRawBuf[iX]; iX += nRepeatCount; } else { // copy over mixed data. nRepeatCount = 1; for( nRepeatCount = 1; iX + nRepeatCount < image->xsize && nRepeatCount < 127; nRepeatCount++ ) { if( iX + nRepeatCount + 3 >= image->xsize ) continue; // quit if the next 3 pixels match if( pabyRawBuf[iX + nRepeatCount] == pabyRawBuf[iX + nRepeatCount+1] && pabyRawBuf[iX + nRepeatCount] == pabyRawBuf[iX + nRepeatCount+2] ) break; } pabyRLEBuf[nRLEBytes++] = (GByte) (0x80 | nRepeatCount); memcpy( pabyRLEBuf + nRLEBytes, pabyRawBuf + iX, nRepeatCount ); nRLEBytes += nRepeatCount; iX += nRepeatCount; } } // EOL marker. pabyRLEBuf[nRLEBytes++] = 0; /* -------------------------------------------------------------------- */ /* Write RLE Buffer at end of file. */ /* -------------------------------------------------------------------- */ int row = (image->ysize - nBlockYOff - 1) + (nBand-1) * image->ysize; VSIFSeekL(image->file, 0, SEEK_END ); image->rowStart[row] = (GUInt32) VSIFTellL( image->file ); image->rowSize[row] = nRLEBytes; image->rleTableDirty = TRUE; if( (int) VSIFWriteL(pabyRLEBuf, 1, nRLEBytes, image->file) != nRLEBytes ) { CPLFree( pabyRLEBuf ); CPLError(CE_Failure, CPLE_OpenFailed, "file write error: row (%d)\n", nBlockYOff ); return CE_Failure; } CPLFree( pabyRLEBuf ); return CE_None; } /************************************************************************/ /* GetColorInterpretation() */ /************************************************************************/ GDALColorInterp SGIRasterBand::GetColorInterpretation() { SGIDataset* poGDS = (SGIDataset*)poDS; if(poGDS->nBands == 1) return GCI_GrayIndex; else if(poGDS->nBands == 2) { if(nBand == 1) return GCI_GrayIndex; else return GCI_AlphaBand; } else if(poGDS->nBands == 3) { if(nBand == 1) return GCI_RedBand; else if(nBand == 2) return GCI_GreenBand; else return GCI_BlueBand; } else if(poGDS->nBands == 4) { if(nBand == 1) return GCI_RedBand; else if(nBand == 2) return GCI_GreenBand; else if(nBand == 3) return GCI_BlueBand; else return GCI_AlphaBand; } return GCI_Undefined; } /************************************************************************/ /* ==================================================================== */ /* SGIDataset */ /* ==================================================================== */ /************************************************************************/ /************************************************************************/ /* SGIDataset() */ /************************************************************************/ SGIDataset::SGIDataset() : fpImage(NULL), bGeoTransformValid(FALSE) { adfGeoTransform[0] = 0.0; adfGeoTransform[1] = 1.0; adfGeoTransform[2] = 0.0; adfGeoTransform[3] = 0.0; adfGeoTransform[4] = 0.0; adfGeoTransform[5] = 1.0; } /************************************************************************/ /* ~SGIDataset() */ /************************************************************************/ SGIDataset::~SGIDataset() { FlushCache(); // Do we need to write out rle table? if( image.rleTableDirty ) { CPLDebug( "SGI", "Flushing RLE offset table." ); ConvertLong( image.rowStart, image.ysize * image.zsize ); ConvertLong( (GUInt32 *) image.rowSize, image.ysize * image.zsize ); VSIFSeekL( fpImage, 512, SEEK_SET ); VSIFWriteL( image.rowStart, 4, image.ysize * image.zsize, fpImage ); VSIFWriteL( image.rowSize, 4, image.ysize * image.zsize, fpImage ); image.rleTableDirty = FALSE; } if(fpImage != NULL) VSIFCloseL(fpImage); CPLFree(image.tmp); CPLFree(image.rowSize); CPLFree(image.rowStart); } /************************************************************************/ /* GetGeoTransform() */ /************************************************************************/ CPLErr SGIDataset::GetGeoTransform(double * padfTransform) { if(bGeoTransformValid) { memcpy(padfTransform, adfGeoTransform, sizeof(double)*6); return CE_None; } else return GDALPamDataset::GetGeoTransform(padfTransform); } /************************************************************************/ /* Open() */ /************************************************************************/ GDALDataset* SGIDataset::Open(GDALOpenInfo* poOpenInfo) { /* -------------------------------------------------------------------- */ /* First we check to see if the file has the expected header */ /* bytes. */ /* -------------------------------------------------------------------- */ if(poOpenInfo->nHeaderBytes < 12) return NULL; ImageRec tmpImage; memcpy(&tmpImage, poOpenInfo->pabyHeader, 12); tmpImage.Swap(); if(tmpImage.imagic != 474) return NULL; if (tmpImage.type != 0 && tmpImage.type != 1) return NULL; if (tmpImage.bpc != 1 && tmpImage.bpc != 2) return NULL; if (tmpImage.dim != 1 && tmpImage.dim != 2 && tmpImage.dim != 3) return NULL; if(tmpImage.bpc != 1) { CPLError(CE_Failure, CPLE_NotSupported, "The SGI driver only supports 1 byte channel values.\n"); return NULL; } /* -------------------------------------------------------------------- */ /* Create a corresponding GDALDataset. */ /* -------------------------------------------------------------------- */ SGIDataset* poDS; poDS = new SGIDataset(); poDS->eAccess = poOpenInfo->eAccess; /* -------------------------------------------------------------------- */ /* Open the file using the large file api. */ /* -------------------------------------------------------------------- */ if( poDS->eAccess == GA_ReadOnly ) poDS->fpImage = VSIFOpenL(poOpenInfo->pszFilename, "rb"); else poDS->fpImage = VSIFOpenL(poOpenInfo->pszFilename, "rb+"); if(poDS->fpImage == NULL) { CPLError(CE_Failure, CPLE_OpenFailed, "VSIFOpenL(%s) failed unexpectedly in sgidataset.cpp\n%s", poOpenInfo->pszFilename, VSIStrerror( errno ) ); delete poDS; return NULL; } /* -------------------------------------------------------------------- */ /* Read pre-image data after ensuring the file is rewound. */ /* -------------------------------------------------------------------- */ VSIFSeekL(poDS->fpImage, 0, SEEK_SET); if(VSIFReadL((void*)(&(poDS->image)), 1, 12, poDS->fpImage) != 12) { CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading header in sgidataset.cpp"); delete poDS; return NULL; } poDS->image.Swap(); poDS->image.file = poDS->fpImage; poDS->image.fileName = poOpenInfo->pszFilename; /* -------------------------------------------------------------------- */ /* Capture some information from the file that is of interest. */ /* -------------------------------------------------------------------- */ poDS->nRasterXSize = poDS->image.xsize; poDS->nRasterYSize = poDS->image.ysize; if (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0) { CPLError(CE_Failure, CPLE_OpenFailed, "Invalid image dimensions : %d x %d", poDS->nRasterXSize, poDS->nRasterYSize); delete poDS; return NULL; } poDS->nBands = MAX(1,poDS->image.zsize); if (poDS->nBands > 256) { CPLError(CE_Failure, CPLE_OpenFailed, "Too many bands : %d", poDS->nBands); delete poDS; return NULL; } int numItems = (int(poDS->image.bpc) == 1) ? 256 : 65536; poDS->image.tmp = (unsigned char*)VSICalloc(poDS->image.xsize,numItems); if (poDS->image.tmp == NULL) { CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory"); delete poDS; return NULL; } /* -------------------------------------------------------------------- */ /* Read RLE Pointer tables. */ /* -------------------------------------------------------------------- */ if(int(poDS->image.type) == 1) // RLE compressed { int x = poDS->image.ysize * poDS->nBands * sizeof(GUInt32); poDS->image.rowStart = (GUInt32*)VSIMalloc2(poDS->image.ysize, poDS->nBands * sizeof(GUInt32)); poDS->image.rowSize = (GInt32*)VSIMalloc2(poDS->image.ysize, poDS->nBands * sizeof(GUInt32)); if (poDS->image.rowStart == NULL || poDS->image.rowSize == NULL) { CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory"); delete poDS; return NULL; } memset(poDS->image.rowStart, 0, x); memset(poDS->image.rowSize, 0, x); poDS->image.rleEnd = 512 + (2 * x); VSIFSeekL(poDS->fpImage, 512, SEEK_SET); if((int) VSIFReadL(poDS->image.rowStart, 1, x, poDS->image.file) != x) { delete poDS; CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading start positions in sgidataset.cpp"); return NULL; } if((int) VSIFReadL(poDS->image.rowSize, 1, x, poDS->image.file) != x) { delete poDS; CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading row lengths in sgidataset.cpp"); return NULL; } ConvertLong(poDS->image.rowStart, x/(int)sizeof(GUInt32)); ConvertLong((GUInt32*)poDS->image.rowSize, x/(int)sizeof(GInt32)); } else // uncompressed. { poDS->image.rowStart = NULL; poDS->image.rowSize = NULL; } /* -------------------------------------------------------------------- */ /* Create band information objects. */ /* -------------------------------------------------------------------- */ for(int iBand = 0; iBand < poDS->nBands; iBand++) poDS->SetBand(iBand+1, new SGIRasterBand(poDS, iBand+1)); /* -------------------------------------------------------------------- */ /* Check for world file. */ /* -------------------------------------------------------------------- */ poDS->bGeoTransformValid = GDALReadWorldFile(poOpenInfo->pszFilename, ".wld", poDS->adfGeoTransform); /* -------------------------------------------------------------------- */ /* Initialize any PAM information. */ /* -------------------------------------------------------------------- */ poDS->SetDescription(poOpenInfo->pszFilename); poDS->TryLoadXML(); /* -------------------------------------------------------------------- */ /* Check for overviews. */ /* -------------------------------------------------------------------- */ poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename ); return poDS; } /************************************************************************/ /* Create() */ /************************************************************************/ GDALDataset *SGIDataset::Create( const char * pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eType, CPL_UNUSED char **papszOptions ) { if( eType != GDT_Byte ) { CPLError( CE_Failure, CPLE_AppDefined, "Attempt to create SGI dataset with an illegal\n" "data type (%s), only Byte supported by the format.\n", GDALGetDataTypeName(eType) ); return NULL; } /* -------------------------------------------------------------------- */ /* Open the file for output. */ /* -------------------------------------------------------------------- */ VSILFILE *fp = VSIFOpenL( pszFilename, "w" ); if( fp == NULL ) { CPLError( CE_Failure, CPLE_OpenFailed, "Failed to create file '%s': %s", pszFilename, VSIStrerror( errno ) ); return NULL; } /* -------------------------------------------------------------------- */ /* Prepare and write 512 byte header. */ /* -------------------------------------------------------------------- */ GByte abyHeader[512]; GInt16 nShortValue; GInt32 nIntValue; memset( abyHeader, 0, 512 ); abyHeader[0] = 1; abyHeader[1] = 218; abyHeader[2] = 1; // RLE abyHeader[3] = 1; // 8bit if( nBands == 1 ) nShortValue = CPL_MSBWORD16(2); else nShortValue = CPL_MSBWORD16(3); memcpy( abyHeader + 4, &nShortValue, 2 ); nShortValue = CPL_MSBWORD16(nXSize); memcpy( abyHeader + 6, &nShortValue, 2 ); nShortValue = CPL_MSBWORD16(nYSize); memcpy( abyHeader + 8, &nShortValue, 2 ); nShortValue = CPL_MSBWORD16(nBands); memcpy( abyHeader + 10, &nShortValue, 2 ); nIntValue = CPL_MSBWORD32(0); memcpy( abyHeader + 12, &nIntValue, 4 ); nIntValue = CPL_MSBWORD32(255); memcpy( abyHeader + 16, &nIntValue, 4 ); VSIFWriteL( abyHeader, 1, 512, fp ); /* -------------------------------------------------------------------- */ /* Create our RLE compressed zero-ed dummy line. */ /* -------------------------------------------------------------------- */ GByte *pabyRLELine; GInt32 nRLEBytes = 0; int nPixelsRemaining = nXSize; pabyRLELine = (GByte *) CPLMalloc((nXSize/127) * 2 + 4); while( nPixelsRemaining > 0 ) { pabyRLELine[nRLEBytes] = (GByte) MIN(127,nPixelsRemaining); pabyRLELine[nRLEBytes+1] = 0; nPixelsRemaining -= pabyRLELine[nRLEBytes]; nRLEBytes += 2; } /* -------------------------------------------------------------------- */ /* Prepare and write RLE offset/size tables with everything */ /* zeroed indicating dummy lines. */ /* -------------------------------------------------------------------- */ int i; int nTableLen = nYSize * nBands; GInt32 nDummyRLEOffset = 512 + 4 * nTableLen * 2; CPL_MSBPTR32( &nRLEBytes ); CPL_MSBPTR32( &nDummyRLEOffset ); for( i = 0; i < nTableLen; i++ ) VSIFWriteL( &nDummyRLEOffset, 1, 4, fp ); for( i = 0; i < nTableLen; i++ ) VSIFWriteL( &nRLEBytes, 1, 4, fp ); /* -------------------------------------------------------------------- */ /* write the dummy RLE blank line. */ /* -------------------------------------------------------------------- */ CPL_MSBPTR32( &nRLEBytes ); if( (GInt32) VSIFWriteL( pabyRLELine, 1, nRLEBytes, fp ) != nRLEBytes ) { CPLError( CE_Failure, CPLE_FileIO, "Failure writing SGI file '%s'.\n%s", pszFilename, VSIStrerror( errno ) ); return NULL; } VSIFCloseL( fp ); CPLFree( pabyRLELine ); return (GDALDataset*) GDALOpen( pszFilename, GA_Update ); } /************************************************************************/ /* GDALRegister_SGI() */ /************************************************************************/ void GDALRegister_SGI() { GDALDriver* poDriver; if(GDALGetDriverByName("SGI") == NULL) { poDriver = new GDALDriver(); poDriver->SetDescription("SGI"); poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "SGI Image File Format 1.0"); poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "rgb"); poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/rgb"); poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_various.html#SGI" ); poDriver->pfnOpen = SGIDataset::Open; poDriver->pfnCreate = SGIDataset::Create; poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" ); GetGDALDriverManager()->RegisterDriver(poDriver); } }