/* * This file is part of rasdaman community. * * Rasdaman community is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Rasdaman community is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with rasdaman community. If not, see . * * Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / rasdaman GmbH. * * For more information please see * or contact Peter Baumann via . / /** * SOURCE: test_polygon.cc * * MODULE: rasodmg * * COMMENTS: * None */ #include "mymalloc/mymalloc.h" #ifdef EARLY_TEMPLATE #define __EXECUTABLE__ #include "raslib/template_inst.hh" #endif extern "C" { #include "tiffio.h" } #include #include using std::vector; using std::iterator; #include #include #include #if defined(SOLARIS) #include #else #include #endif #include "rasodmg/polygon.hh" #include "rasodmg/transaction.hh" #include "rasodmg/database.hh" #include "rasodmg/marray.hh" #include "rasodmg/oqlquery.hh" #include "rasodmg/fastscale.hh" #include "raslib/basetype.hh" #include "raslib/mitera.hh" #include "raslib/shhopt.h" #include "rasodmg/ref.hh" #include "conversion/tiff.hh" #include "cmlinterpreter.hh" #include "cmloption.hh" #include "cmlerror.hh" // global limits static const int MYSTRINGSIZE = 256; static const int SUCCES = 0; static const int FAILED = 1; // global variable used in this program r_Database db; r_Transaction ta; r_Float rScale; char* rBgr=NULL; r_Polygon poly; // default values & constants static const char* DefaultSrv = "localhost"; static const char* DefaultPort = "7001"; static const char* DefaultDb = "RASBASE"; static const char* DefaultUsr = "rasguest"; static const char* DefaultPasswd = "rasguest"; static const r_Float DefaultScale = 1.; // parameters of this program CmlInterpreter cmlInter; CmlStringOption cmlSrv('s',"server", "srv-name", "name of machine running RasDaMan manager. Default: localhost"); CmlStringOption cmlPort(0,"port", "nnnn", "port number used by RasDaMan manager. Default: 7001"); CmlStringOption cmlDb('d',"database", "db-name", "name of database. Default: RASBASE)."); CmlStringOption cmlUsr(0, "user", "user-name", "name of user. Default rasguest"); CmlStringOption cmlPasswd(0,"passwd", "user-passwd", "password of user. Default rasguest"); CmlStringOption cmlColl('c',"collname", "coll-name", "name of collection. Mandatory"); CmlStringOption cmlDomain('r',"domain", "domain", "domain to be retrieved (e.g. \"[1000:8000,5000:10000]\"). Mandatory"); CmlStringOption cmlScale('f',"scale", "factor", "scale factor applied to domain. Default 1"); CmlStringOption cmlFile('t',"tiffile", "file-name", "name of TIFF file written. Mandatory"); CmlStringOption cmlPolygon('p',"polygon", "pol-desc","polygon for clipping (e.g. \"[1010,8200] [4000,8200] [3000,9800]\")."); CmlStringOption cmlBgr('b',"background", "bgr-desc", "background of TIFF file (e.g. 0x1f)."); CmlBoolOption cmlHelp('h',"help","print this message."); // This is a quick first try at a class for creating TIFFs stripe by stripe. It copies // a lot of code from class r_Conv_TIFF in conversion/tiff.cc. Ideally this at some // point should make use of Andreas' conversion framework. class r_TIFFStripe { public: r_TIFFStripe(const char* newFileName, const r_Minterval& tiffDom); void openTiff(); //open the tiff file bool addArray(const r_GMarray& myArray); //write myArray to tiff file void closeTiff(); //close the tiff file ~r_TIFFStripe(); private: char* fileName; //filename of the tiff image TIFF* tiffFile; //handler of the tiff image uint16 bpp; // bits per pixel (24 for RGB) uint16 bps; // bits per sample (8 for RGB) unsigned long typeSize; // size of base type, will be retrieved with r_Base_Type::size() uint32 width; // image width uint32 height; //image height char* scanLine; // buffer for scanLine to be written to file uint32 tiffRow; // row's no of tiff image }; r_TIFFStripe::r_TIFFStripe(const char* newFileName, const r_Minterval& tiffDom) : bpp(8), bps(8), typeSize(1), tiffRow(0), fileName(NULL), tiffFile(NULL), scanLine(NULL) { fileName = strdup(newFileName); width = (uint32)(tiffDom.get_extent()[0]); height = (uint32)(tiffDom.get_extent()[1]); // copied this formula from Andreas, do not fully get it. 31 seems to be an internal // buffer for tifflib and >> 5 together with uint32 instead of char seems to be used // to get the correct number of chars as oppose to bits. //allocate the buffer scanLine = (char*)mymalloc(width*typeSize); } void r_TIFFStripe::openTiff() { uint16 cmap[256]; // Colour map (for greyscale images) tiffFile = TIFFOpen(fileName, "w"); // These fields are written to the file by TIFFWriteDirectory, which is automatically // called by TIFFClose and TIFFFlush TIFFSetField(tiffFile, TIFFTAG_ARTIST, "RasDaMan"); TIFFSetField(tiffFile, TIFFTAG_DOCUMENTNAME, "Image"); TIFFSetField(tiffFile, TIFFTAG_SOFTWARE, "RasDaMan"); //TIFFSetField(tiffFile, TIFFTAG_SUBFILETYPE, (uint32)0); TIFFSetField(tiffFile, TIFFTAG_IMAGEWIDTH, width); TIFFSetField(tiffFile, TIFFTAG_IMAGELENGTH, height); TIFFSetField(tiffFile, TIFFTAG_BITSPERSAMPLE, bps); // UNIX doesn't mind which fill-order. NT only understands this one. TIFFSetField(tiffFile, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); // problem: LZW is no longer supported in current versions of libtiff. TIFFSetField(tiffFile, TIFFTAG_COMPRESSION, (uint16)COMPRESSION_NONE); TIFFSetField(tiffFile, TIFFTAG_ORIENTATION, (uint16)ORIENTATION_TOPLEFT); // Format-dependent tags, currently support only 8bit grey images TIFFSetField(tiffFile, TIFFTAG_PHOTOMETRIC, (uint16)PHOTOMETRIC_PALETTE); TIFFSetField(tiffFile, TIFFTAG_SAMPLESPERPIXEL, (uint16)1); TIFFSetField(tiffFile, TIFFTAG_PLANARCONFIG, (uint16)PLANARCONFIG_CONTIG); // TIFFSetField(tiffFile, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiffFile, (uint32)-1)); TIFFSetField(tiffFile, TIFFTAG_ROWSPERSTRIP, (uint32)1); //TIFFSetField(tiffFile, TIFFTAG_MINSAMPLEVALUE, (uint16)0); //TIFFSetField(tiffFile, TIFFTAG_MAXSAMPLEVALUE, (uint16)255); TIFFSetField(tiffFile, TIFFTAG_RESOLUTIONUNIT, (uint16)RESUNIT_INCH); // This will have to be adapted! TIFFSetField(tiffFile, TIFFTAG_XRESOLUTION, (float)90.0); TIFFSetField(tiffFile, TIFFTAG_YRESOLUTION, (float)90.0); TIFFSetField(tiffFile, TIFFTAG_XPOSITION, (float)0.0); TIFFSetField(tiffFile, TIFFTAG_YPOSITION, (float)0.0); // build the colour-map (greyscale, i.e. all 3 components identical) // TIFF needs 16 bit values for this (--> tools/tiffdither.c) for (int i=0; i<256; i++) cmap[i] = (uint16)(i*((1L << 16) - 1)/255); TIFFSetField(tiffFile, TIFFTAG_COLORMAP, cmap, cmap, cmap); } bool r_TIFFStripe::addArray(const r_GMarray& myArray) { // here we write the r_GMarray we get line by line into the TIFF. We // assume that the width of myArray (width = [0] r_Sinterval) // corresponds to the width of the whole TIFF. unsigned long imageHeight = myArray.spatial_domain().get_extent()[1]; unsigned long imageWidth = myArray.spatial_domain().get_extent()[0]; if(imageWidth != width) { cout << "Error Tiff file \"" << fileName << "\" initialised for " << width << ", not for " << imageWidth << " !" << endl; return false; } const char* linePtr = myArray.get_array(); // points to myArray's data char* scanLinePtr = (char*)scanLine; for (int row = 0; row < imageHeight; row++) { // copy data (and transpose) for (int col=0; col < imageWidth; col++) { for(int i=0; i" << endl; } void printStatus(char* name) { cout << name << "'s parameters list:" << endl; cmlInter.printStatus(); } void defineParams() { cmlInter.defineOption(&cmlSrv); cmlInter.defineOption(&cmlPort); cmlInter.defineOption(&cmlDb); cmlInter.defineOption(&cmlUsr); cmlInter.defineOption(&cmlPasswd); cmlInter.defineOption(&cmlColl); cmlInter.defineOption(&cmlDomain); cmlInter.defineOption(&cmlScale); cmlInter.defineOption(&cmlFile); cmlInter.defineOption(&cmlPolygon); cmlInter.defineOption(&cmlBgr); cmlInter.defineOption(&cmlHelp); } void getParams() { CmlOption* ptr=NULL; ptr=cmlInter.getOption(&cmlSrv); cmlSrv=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlPort); cmlPort=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlDb); cmlDb=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlUsr); cmlUsr=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlPasswd); cmlPasswd=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlColl); cmlColl=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlDomain); cmlDomain=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlScale); cmlScale=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlFile); cmlFile=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlPolygon); cmlPolygon=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlBgr); cmlBgr=*((CmlStringOption*)ptr); ptr=cmlInter.getOption(&cmlHelp); cmlHelp=*((CmlBoolOption*)ptr); ptr=NULL; } bool parseParams(int argc, char** argv) { try { defineParams(); cmlInter.interpretArguments(argc, argv); getParams(); } catch(CmlError& err) { cout << "Command Line Parsing Error:" << endl << err.getText() << endl; return false; } //check rule if(cmlColl.isPresent() && cmlDomain.isPresent() && cmlFile.isPresent() ) return true; else { cout << "Error some of mandatory arguments are missing!" << endl; cout << "Please check your command line!" << endl; return false; } } bool fillPolygon(r_Polygon& myPoly) { char* startPos = (char*)cmlPolygon.getString(); char* endPos=NULL; int pointStrLen=0, pointNo=0; char currPoint[MYSTRINGSIZE]; r_Point myPoint; //cout << "start decoding polygon string"<< endl; while(true) { strcpy(currPoint, ""); startPos = index(startPos, '['); if(!startPos) { // Did not find a closing [, that's it. break; } endPos = index(startPos, ']'); if(!endPos) { // Did not find a closing ], that's it. break; } // try to add point pointStrLen = endPos - startPos + 1; strncpy(currPoint, startPos, pointStrLen); currPoint[pointStrLen+1] = '\0'; try { myPoint=r_Point(currPoint); myPoly.addPoint( myPoint ); pointNo++; //cout << "-point " << pointNo << " : " << currPoint << endl; } catch(r_Error& err) { cout << "Error decoding point \"" << currPoint << "\" from polygon string \"" << cmlPolygon.getString() << " !" << endl; return false; } startPos += pointStrLen; } if(!pointNo) { cout << "Error no points found, while decoding polygon string \"" << cmlPolygon.getString() << "\" !" << endl; return false; } //cout << "end decoding"<< endl; myPoly.close(); return true; } bool parseBgr() { static char hexval[]= { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, }; static char hexfig[]= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '\0'}; static r_Bytes sizeBgrMin=2, sizeHexFig=strlen(hexfig); char* pBgr=(char*)cmlBgr.getString(); r_Bytes sizeBgr=strlen(pBgr), sizerBgr=0, indexFigure=0, indexHexFig=0; if(sizeBgr <= sizeBgrMin) { cout << "Error decoding background \"" << pBgr << "\", is not a hex string !" << endl; return false; } if (pBgr[0] !='0' || (pBgr[1] != 'x' && pBgr[1]!='X')) { cout << "Error decoding background \"" << pBgr << "\", 0x or 0X is missing !" << endl; return false; } if(sizeBgr%sizeBgrMin) { sizerBgr=(sizeBgr-sizeBgrMin+1)/sizeBgrMin; } else sizerBgr=(sizeBgr-sizeBgrMin)/sizeBgrMin; rBgr=new char [sizeBgr+1]; memset(rBgr,'\0', sizeBgr+1); //skip tag 0x/0X pBgr+=sizeBgrMin; while (*pBgr) { //check figure indexHexFig=0; while(indexHexFig=sizeHexFig) { cout << "Error decoding background \"" << pBgr << "\", \"" << *pBgr << "\" is no a hex value !" << endl; delete[] rBgr; return false; } //set value figure if(indexFigure%sizeBgrMin) { #if defined(LITTLE_ENDIAN) rBgr[indexFigure/sizeBgrMin]=rBgr[indexFigure/sizeBgrMin] * 16 + hexval[indexHexFig]; #else rBgr[indexFigure/sizeBgrMin]=rBgr[indexFigure/sizeBgrMin]+ hexval[indexHexFig] * 16; #endif } else rBgr[indexFigure/sizeBgrMin]=hexval[indexHexFig]; //advance pBgr++; indexFigure++; } return true; } bool detectParams() { r_Long rPort=0; // server name if(!cmlSrv.isPresent()) cmlSrv.setValue(DefaultSrv); // server port if(!cmlPort.isPresent()) cmlPort.setValue(DefaultPort); else { try { rPort=cmlPort.getLong(); } catch(CmlError& err) { cout << "Error decoding " << cmlPort.getLongForm() << " \"" << cmlPort.getString() << "\" isn't a number!" << endl; return false; } if (rPort <= 0.) { cout << "Error decoding " << cmlPort.getLongForm() << " \"" << cmlPort.getString() << "\" is negative or zero !" << endl; return false; } } // database name if(!cmlDb.isPresent()) cmlDb.setValue(DefaultDb); // user name if(!cmlUsr.isPresent()) cmlUsr.setValue(DefaultUsr); // user passord if(!cmlPasswd.isPresent()) cmlPasswd.setValue(DefaultPasswd); // scale factor if(!cmlScale.isPresent()) rScale=DefaultScale; else { try { rScale=cmlScale.getDouble(); } catch(CmlError& err) { cout << "Error decoding " << cmlScale.getLongForm() << " \"" << cmlScale.getString() << "\" isn't a number!" << endl; return false; } if (rScale <= 0.) { cout << "Error decoding " << cmlScale.getLongForm() << " \"" << cmlScale.getString() << "\" is negative or zero !" << endl; return false; } } // collection name // nothing to check here // collection domain try { r_Minterval test(cmlDomain.getString()); } catch(r_Error& err) { cout << "Error while decoding " << cmlDomain.getLongForm() << " \"" << cmlDomain.getString() << "\" !" << endl; cout << "Error " << err.get_errorno() << " : " << err.what() << endl; return false; } // filename //nothing to be checked here // polygon if(cmlPolygon.isPresent()) { if(!fillPolygon(poly)) return false; //FIXME //is convex? if(poly.detectPolygonType()!=r_Polygon::CONVEX) { cout << "We support only simple convex polygon now. Please check your polygon !" << endl; return false; } } // background if(cmlBgr.isPresent()) return parseBgr(); return true; } bool exportData() { // At the moment we do just a quick check for the TIFF writing using r_Conversion. // Now we do a query and store the result as a TIFF using r_Conversion. // Ok, the whole TIFF conversion thing is done below. Now let's get an // array from RasDaMan here. r_Ref mddObj; r_Fast_Base_Scale *scaler=NULL; r_Minterval trimDom(cmlDomain.getString()); r_Minterval tiffDom, currDom, insertionUnit(2), clipDom(2); r_Minterval collDom; r_Point trimExtent; r_Polygon myPoly; // 5 MB buffer for spooling images r_ULong bufSize = 5 * 1024 * 1024; //size of base type r_Bytes typeSize = 1; // no of scan lines in buffer r_ULong numScanLineInBuf=0; //tiff strip object r_TIFFStripe* myTiff=NULL; try { db.set_servername(cmlSrv.getString(), cmlPort.getLong()); db.set_useridentification(cmlUsr.getString(), cmlPasswd.getString()); db.open(cmlDb.getString()); try { ta.begin( r_Transaction::read_only ); scaler = new r_Fast_Scale(cmlColl.getString()); collDom = scaler->get_full_domain(); } catch( r_Error& errorObj ) { cout << "Error while initializing collection " << cmlColl.getString() << " !" << endl; cout << "Error " << errorObj.get_errorno() << " : " << errorObj.what() << endl; ta.abort(); db.close(); if(rBgr) { delete[] rBgr; rBgr=NULL; } return false; } if (!collDom.covers(trimDom)) { cout << "Error, the requested domain " << trimDom << " is not in the collection domain " << collDom << " !" << endl; if(scaler) { delete scaler; scaler=NULL; } ta.abort(); db.close(); if(rBgr) { delete[] rBgr; rBgr=NULL; } return false; } // Ok, now here we split the domain in stripes. We take a roughly 5 MB buffer. It // is calculated using the original domain of the area to be retrieved and scale^2 // to calculate the memory used without calculating the domains back and forth. insertionUnit[0] = trimDom[0]; // we take the whole width of the image. trimExtent=trimDom.get_extent(); numScanLineInBuf = bufSize / ( typeSize * trimExtent[0] * rScale * rScale); numScanLineInBuf = numScanLineInBuf > trimExtent[1] ? trimExtent[1] : numScanLineInBuf; if(numScanLineInBuf < 1) //if numScanLine is 0 as result of division numScanLineInBuf=1; insertionUnit[1] = r_Sinterval((r_Range)0, (r_Range)(numScanLineInBuf - 1)); try { scaler->get_scaled_domain(trimDom, tiffDom, rScale); } catch( r_Error& errorObj ){ cout << "Error while getting the scaled domain !" << endl; cout << "Error " << errorObj.get_errorno() << " : " << errorObj.what() << endl; if(scaler) { delete scaler; scaler=NULL; } ta.abort(); db.close(); if(rBgr) { delete[] rBgr; rBgr=NULL; } return false; } if(cmlPolygon.isPresent()) { // we scale the polygon according to the scale factor cout << "Defined Polygon: " << poly << endl; poly.scale(collDom.get_origin(), rScale); cout << "Scaled Polygon: " << poly << endl; } myTiff = new r_TIFFStripe(cmlFile.getString(), tiffDom); myTiff->openTiff(); cout << "Retrieving area " << trimDom << " of object " << cmlColl.getString() << " with scale factor " << rScale << "." << endl; cout << "Retrieving units of shape " << insertionUnit << "." << endl; cout << "Tiff Image area " << tiffDom << endl; r_MiterArea myIter(&insertionUnit, &trimDom); while( !myIter.isDone() ) { currDom = myIter.nextArea(); cout << " Getting " << currDom << " with scale factor " << rScale << "." << endl; try { mddObj = scaler->get_scaled_object(currDom, rScale, 1); } catch( r_Error& errorObj ) { cout << "Error while getting the scaled domain !" << endl; cout << "Error " << errorObj.get_errorno() << " : " << errorObj.what() << endl; if(myTiff) { myTiff->closeTiff(); delete myTiff; myTiff=NULL; } if(scaler) { delete scaler; scaler=NULL; } ta.abort(); db.close(); if(rBgr) { delete[] rBgr; rBgr=NULL; } return false; } // The polygonal cutout is currently not done. We will need a scale function with the // same semantics as fastscale for it to work. Then the polygon can be specified in // original pixel coordinates and will be scaled according to the scaling factor. if(cmlPolygon.isPresent()) { // we clip the polygon it according to the scaled domain retrieved by r_Fast_Scale // original polygon is modified by clip function myPoly=poly; scaler->get_scaled_domain(currDom, clipDom, rScale); cout << "Domain for clipping: " << clipDom << endl; //FIXME //quick hack for test_polygon because get_scaled_image //returns the clipDom translated in currDom.get_origin() mddObj->set_spatial_domain(clipDom); myPoly.clip(clipDom); cout << "Clipped polygon: " << myPoly << endl; // we would have to transpose it here. This is done by the TIFF conversion. // REALLY IMPORTANT: The TA has to be open here! Otherwise neither // type info nor domain can be read. try { myPoly.fillMArray(*mddObj, false, rBgr); } catch(r_Error& err) { cout << "Error size of background " << strlen(rBgr) << " bytes " << " is different from mdd base type size " << mddObj->get_type_length() << " bytes !" << endl; mddObj.destroy(); throw err; } }//end if(usePolygon) // Ok, now we use our r_TIFFStripe class here if(!myTiff->addArray(*mddObj)) { if(myTiff) { myTiff->closeTiff(); delete myTiff; myTiff=NULL; } if(rBgr) { delete[] rBgr; rBgr=NULL; } if(scaler) { delete scaler; scaler=NULL; } mddObj.destroy(); ta.abort(); db.close(); return false; } mddObj.destroy(); }//end while delete scaler; scaler=NULL; myTiff->closeTiff(); delete myTiff; myTiff=NULL; delete[] rBgr; rBgr=NULL; ta.commit(); db.close(); } catch(r_Error & errObj) { cout << "Error while exporting the data !" << endl; cout << "Error " << errObj.get_errorno() << " : " << errObj.what() << endl; if(scaler) { delete scaler; scaler=NULL; } ta.abort(); db.close(); if(rBgr) { delete[] rBgr; rBgr = NULL; } if(myTiff) { myTiff->closeTiff(); delete myTiff; myTiff = NULL; } return false; } return true; } bool processRequest(char* name) { bool result=false; if(detectParams()) { #if defined(RMANDEBUG) printStatus(name); #endif result=exportData(); } return result; } int main(int argc, char *argv[]) { int result=FAILED; if(!parseParams(argc, argv)) printUsage(argv[0]); else if(cmlHelp.isPresent()) { printUsage(argv[0]); result=SUCCES; } else if(processRequest(argv[0])) result=SUCCES; else printUsage(argv[0]); return result; }