1 //***************************************************************
2 // CLass: %CLASS%
3 //
4 // Description:
5 //
6 //
7 // Author: Chris Browet <cbro@semperpax.com> (C) 2010
8 //
9 // Copyright: See COPYING file that comes with this distribution
10 //
11 //******************************************************************
12 
13 #include "GdalAdapter.h"
14 
15 #include <QCoreApplication>
16 #include <QtPlugin>
17 #include <QAction>
18 #include <QFileDialog>
19 #include <QPainter>
20 #include <QMessageBox>
21 #include <QInputDialog>
22 
23 #include <QDebug>
24 
25 #include "gdal_priv.h"
26 #include "ogrsf_frmts.h"
27 
28 #include "ProjectionChooser.h"
29 
30 #define IN_MEMORY_LIMIT 100000000
31 
32 static const QUuid theUid ("{5c9479df-0b1a-4c49-9559-83d5ffa93911}");
33 static const QString theName("GDAL Raster");
34 
getId() const35 QUuid GdalAdapterFactory::getId() const
36 {
37     return theUid;
38 }
39 
getName() const40 QString	GdalAdapterFactory::getName() const
41 {
42     return theName;
43 }
44 
45 /**************/
46 
radToAng(double a)47 inline double radToAng(double a)
48 {
49     return a*180/M_PI;
50 }
51 
angToRad(double a)52 inline double angToRad(double a)
53 {
54     return a*M_PI/180.;
55 }
56 
57 #define FILTER_OPEN_SUPPORTED \
58     tr("All Files (*)")
59 
GdalAdapter()60 GdalAdapter::GdalAdapter()
61     : poDataset(0), isLatLon(false)
62 {
63     GDALAllRegister();
64 
65     QAction* loadImage = new QAction(tr("Load file(s)..."), this);
66     loadImage->setData(theUid.toString());
67     connect(loadImage, SIGNAL(triggered()), SLOT(onLoadImage()));
68     QAction* setSource = new QAction(tr("Specify \"source\" tag..."), this);
69     setSource->setData(theUid.toString());
70     connect(setSource, SIGNAL(triggered()), SLOT(onSetSourceTag()));
71 
72     theMenu = new QMenu();
73     theMenu->addAction(loadImage);
74     theMenu->addAction(setSource);
75 }
76 
77 
~GdalAdapter()78 GdalAdapter::~GdalAdapter()
79 {
80     cleanup();
81 }
82 
getId() const83 QUuid GdalAdapter::getId() const
84 {
85     return theUid;
86 }
87 
getName() const88 QString	GdalAdapter::getName() const
89 {
90     return theName;
91 }
92 
alreadyLoaded(QString fn) const93 bool GdalAdapter::alreadyLoaded(QString fn) const
94 {
95     for (int j=0; j<theImages.size(); ++j)
96         if (theImages[j].theFilename == fn)
97             return true;
98     return false;
99 }
100 
loadImage(const QString & fn)101 bool GdalAdapter::loadImage(const QString& fn)
102 {
103     if (alreadyLoaded(fn))
104         return true;
105 
106     QFileInfo fi(fn);
107     GdalImage img;
108     QRectF bbox;
109 
110     poDataset = (GDALDataset *) GDALOpen( QDir::toNativeSeparators(fi.absoluteFilePath()).toUtf8().constData(), GA_ReadOnly );
111     if( poDataset == NULL )
112     {
113         qDebug() <<  "GDAL Open failed: " << fn;
114         return false;
115     }
116 
117     bool hasGeo = false;
118     QDir dir(fi.absoluteDir());
119     QString f = fi.baseName();
120     QStringList wldFilter;
121     wldFilter <<  f+".tfw" << f+".tifw" << f+".tiffw" << f+".wld";
122     QFileInfoList fil = dir.entryInfoList(wldFilter);
123     if (fil.count()) {
124         QFile wld(fil[0].absoluteFilePath());
125         if (wld.open(QIODevice::ReadOnly)) {
126             int i;
127             for (i=0; i<6; ++i) {
128                 if (wld.atEnd())
129                     break;
130                 QString l = wld.readLine();
131                 bool ok;
132                 double d = l.toDouble(&ok);
133                 if (!ok)
134                     break;
135                 switch (i) {
136                 case 0:
137                     img.adfGeoTransform[1] = d;
138                     break;
139                 case 1:
140                     img.adfGeoTransform[4] = d;
141                     break;
142                 case 2:
143                     img.adfGeoTransform[2] = d;
144                     break;
145                 case 3:
146                     img.adfGeoTransform[5] = d;
147                     break;
148                 case 4:
149                     img.adfGeoTransform[0] = d;
150                     break;
151                 case 5:
152                     img.adfGeoTransform[3] = d;
153                     break;
154                 }
155 
156             }
157             if (i == 6)
158                 hasGeo = true;
159         }
160     }
161     if(!hasGeo)
162         if ( poDataset->GetGeoTransform( img.adfGeoTransform ) != CE_None ) {
163             GDALClose((GDALDatasetH)poDataset);
164             return false;
165         }
166 
167     qDebug( "Origin = (%.6f,%.6f)\n",
168             img.adfGeoTransform[0], img.adfGeoTransform[3] );
169 
170     qDebug( "Pixel Size = (%.6f,%.6f)\n",
171             img.adfGeoTransform[1], img.adfGeoTransform[5] );
172 
173     bbox.setTopLeft(QPointF(img.adfGeoTransform[0], img.adfGeoTransform[3]));
174     bbox.setWidth(img.adfGeoTransform[1]*poDataset->GetRasterXSize());
175     bbox.setHeight(img.adfGeoTransform[5]*poDataset->GetRasterYSize());
176 
177     isLatLon = false;
178     if( strlen(poDataset->GetProjectionRef()) != 0 ) {
179         qDebug( "Projection is `%s'\n", poDataset->GetProjectionRef() );
180         OGRSpatialReference* theSrs = new OGRSpatialReference(poDataset->GetProjectionRef());
181         if (theSrs && theSrs->Validate() == OGRERR_NONE) {
182             theSrs->morphFromESRI();
183             char* theProj4;
184             if (theSrs->exportToProj4(&theProj4) == OGRERR_NONE) {
185                 qDebug() << "GDAL: to proj4 : " << theProj4;
186             } else {
187                 qDebug() << "GDAL: to proj4 error: " << CPLGetLastErrorMsg();
188                 GDALClose((GDALDatasetH)poDataset);
189                 return false;
190             }
191             QString srsProj = QString(theProj4);
192             if (!srsProj.isEmpty() && theProjection != srsProj) {
193                 cleanup();
194                 theProjection = srsProj;
195             }
196             isLatLon = (theSrs->IsGeographic() == TRUE);
197         }
198     }
199     if (theProjection.isEmpty()) {
200         theProjection = ProjectionChooser::getProjection(QCoreApplication::translate("ImportExportGdal", "Unable to set projection; please specify one"));
201         if (theProjection.isEmpty()) {
202             GDALClose((GDALDatasetH)poDataset);
203             return false;
204         }
205     }
206 
207     qDebug( "Driver: %s/%s\n",
208             poDataset->GetDriver()->GetDescription(),
209             poDataset->GetDriver()->GetMetadataItem( GDAL_DMD_LONGNAME ) );
210 
211     qDebug( "Size is %dx%dx%d\n",
212             poDataset->GetRasterXSize(), poDataset->GetRasterYSize(),
213             poDataset->GetRasterCount() );
214 
215     GdalAdapter::ImgType theType = GdalAdapter::Unknown;
216     int bandCount = poDataset->GetRasterCount();
217     int ixA = -1;
218     int ixR, ixG, ixB;
219     int ixH, ixS, ixL;
220     int ixC, ixM, ixY, ixK;
221     int ixYuvY, ixYuvU, ixYuvV;
222     double adfMinMax[2];
223     double UnknownUnit;
224     GDALColorTable* colTable = NULL;
225     for (int i=0; i<bandCount; ++i) {
226         GDALRasterBand  *poBand = poDataset->GetRasterBand( i+1 );
227         GDALColorInterp bandtype = poBand->GetColorInterpretation();
228         qDebug() << "Band " << i+1 << " Color: " <<  GDALGetColorInterpretationName(poBand->GetColorInterpretation());
229 
230         switch (bandtype)
231         {
232         case GCI_Undefined:
233             theType = GdalAdapter::Unknown;
234             int             bGotMin, bGotMax;
235             adfMinMax[0] = poBand->GetMinimum( &bGotMin );
236             adfMinMax[1] = poBand->GetMaximum( &bGotMax );
237             if( ! (bGotMin && bGotMax) )
238                 GDALComputeRasterMinMax((GDALRasterBandH)poBand, TRUE, adfMinMax);
239             UnknownUnit = (adfMinMax[1] - adfMinMax[0]) / 256;
240             break;
241         case GCI_GrayIndex:
242             theType = GdalAdapter::GrayScale;
243             break;
244         case GCI_RedBand:
245             theType = GdalAdapter::Rgb;
246             ixR = i;
247             break;
248         case GCI_GreenBand:
249             theType = GdalAdapter::Rgb;
250             ixG = i;
251             break;
252         case GCI_BlueBand :
253             theType = GdalAdapter::Rgb;
254             ixB = i;
255             break;
256         case GCI_HueBand:
257             theType = GdalAdapter::Hsl;
258             ixH = i;
259             break;
260         case GCI_SaturationBand:
261             theType = GdalAdapter::Hsl;
262             ixS = i;
263             break;
264         case GCI_LightnessBand:
265             theType = GdalAdapter::Hsl;
266             ixL = i;
267             break;
268         case GCI_CyanBand:
269             theType = GdalAdapter::Cmyk;
270             ixC = i;
271             break;
272         case GCI_MagentaBand:
273             theType = GdalAdapter::Cmyk;
274             ixM = i;
275             break;
276         case GCI_YellowBand:
277             theType = GdalAdapter::Cmyk;
278             ixY = i;
279             break;
280         case GCI_BlackBand:
281             theType = GdalAdapter::Cmyk;
282             ixK = i;
283             break;
284         case GCI_YCbCr_YBand:
285             theType = GdalAdapter::YUV;
286             ixYuvY = i;
287             break;
288         case GCI_YCbCr_CbBand:
289             theType = GdalAdapter::YUV;
290             ixYuvU = i;
291             break;
292         case GCI_YCbCr_CrBand:
293             theType = GdalAdapter::YUV;
294             ixYuvV = i;
295             break;
296         case GCI_AlphaBand:
297             ixA = i;
298             break;
299         case GCI_PaletteIndex:
300             colTable = poBand->GetColorTable();
301             switch (colTable->GetPaletteInterpretation())
302             {
303             case GPI_Gray :
304                 theType = GdalAdapter::Palette_Gray;
305                 break;
306             case GPI_RGB :
307                 theType = GdalAdapter::Palette_RGBA;
308                 break;
309             case GPI_CMYK :
310                 theType = GdalAdapter::Palette_CMYK;
311                 break;
312             case GPI_HLS :
313                 theType = GdalAdapter::Palette_HLS;
314                 break;
315             }
316             break;
317         }
318     }
319 
320     QSize theImgSize(poDataset->GetRasterXSize(), poDataset->GetRasterYSize());
321     QImage theImg = QImage(theImgSize, QImage::Format_ARGB32);
322 
323     // Make sure that lineBuf holds one whole line of data.
324     float *lineBuf;
325     lineBuf = (float *) CPLMalloc(theImgSize.width() * bandCount * sizeof(float));
326 
327     int px, py;
328     //every row loop
329     for (int row = 0; row < theImgSize.height(); row++) {
330         py = row;
331         CPLErr err = poDataset->RasterIO( GF_Read, 0, row, theImgSize.width(),
332                 1, lineBuf, theImgSize.width(), 1, GDT_Float32, bandCount,
333                 NULL, sizeof(float) * bandCount, 0, sizeof(float) );
334         /* FIXME: Perhaps break, or check if more work needs to be done
335          * (filling with an error pattern? */
336         if (err != CE_None) {
337             qDebug() << "RasterIO failed to read row. Skipping.";
338             continue;
339         }
340 
341         // every pixel in row.
342         for (int col = 0; col < theImgSize.width(); col++){
343             px = col;
344             switch (theType)
345             {
346             case GdalAdapter::Unknown:
347             {
348                 float* v = lineBuf + (col*bandCount);
349                 float val = (*v - adfMinMax[0]) / UnknownUnit;
350                 theImg.setPixel(px, py, qRgb(val, val, val));
351                 break;
352             }
353             case GdalAdapter::GrayScale:
354             {
355                 float* v = lineBuf + (col*bandCount);
356                 theImg.setPixel(px, py, qRgb(*v, *v, *v));
357                 break;
358             }
359             case GdalAdapter::Rgb:
360             {
361                 float* r = lineBuf + (col*bandCount) + ixR;
362                 float* g = lineBuf + (col*bandCount) + ixG;
363                 float* b = lineBuf + (col*bandCount) + ixB;
364                 int a = 255;
365                 if (ixA != -1) {
366                     float* fa = lineBuf + (col*bandCount) + ixA;
367                     a = *fa;
368                 }
369                 theImg.setPixel(px, py, qRgba(*r, *g, *b, a));
370                 break;
371             }
372 #if QT_VERSION >= 0x040600
373             case GdalAdapter::Hsl:
374             {
375                 float* h = lineBuf + (col*bandCount) + ixH;
376                 float* s = lineBuf + (col*bandCount) + ixS;
377                 float* l = lineBuf + (col*bandCount) + ixL;
378                 int a = 255;
379                 if (ixA != -1) {
380                     float* fa = lineBuf + (col*bandCount) + ixA;
381                     a = *fa;
382                 }
383                 QColor C = QColor::fromHsl(*h, *s, *l, a);
384                 theImg.setPixel(px, py, C.rgba());
385                 break;
386             }
387 #endif
388             case GdalAdapter::Cmyk:
389             {
390                 float* c = lineBuf + (col*bandCount) + ixC;
391                 float* m = lineBuf + (col*bandCount) + ixM;
392                 float* y = lineBuf + (col*bandCount) + ixY;
393                 float* k = lineBuf + (col*bandCount) + ixK;
394                 int a = 255;
395                 if (ixA != -1) {
396                     float* fa = lineBuf + (col*bandCount) + ixA;
397                     a = *fa;
398                 }
399                 QColor C = QColor::fromCmyk(*c, *m, *y, *k, a);
400                 theImg.setPixel(px, py, C.rgba());
401                 break;
402             }
403             case GdalAdapter::YUV:
404             {
405                 // From http://www.fourcc.org/fccyvrgb.php
406                 float* y = lineBuf + (col*bandCount) + ixYuvY;
407                 float* u = lineBuf + (col*bandCount) + ixYuvU;
408                 float* v = lineBuf + (col*bandCount) + ixYuvV;
409                 int a = 255;
410                 if (ixA != -1) {
411                     float* fa = lineBuf + (col*bandCount) + ixA;
412                     a = *fa;
413                 }
414                 float R = 1.164*(*y - 16) + 1.596*(*v - 128);
415                 float G = 1.164*(*y - 16) - 0.813*(*v - 128) - 0.391*(*u - 128);
416                 float B = 1.164*(*y - 16) + 2.018*(*u - 128);
417 
418                 theImg.setPixel(px, py, qRgba(R, G, B, a));
419                 break;
420             }
421             case GdalAdapter::Palette_Gray:
422             {
423                 float* ix = (lineBuf + (col*bandCount));
424                 const GDALColorEntry* color = colTable->GetColorEntry(*ix);
425                 theImg.setPixel(px, py, qRgb(color->c1, color->c1, color->c1));
426                 break;
427             }
428             case GdalAdapter::Palette_RGBA:
429             {
430                 float* ix = (lineBuf + (col*bandCount));
431                 const GDALColorEntry* color = colTable->GetColorEntry(*ix);
432                 theImg.setPixel(px, py, qRgba(color->c1, color->c2, color->c3, color->c4));
433                 break;
434             }
435 #if QT_VERSION >= 0x040600
436             case GdalAdapter::Palette_HLS:
437             {
438                 float* ix = (lineBuf + (col*bandCount));
439                 const GDALColorEntry* color = colTable->GetColorEntry(*ix);
440                 QColor C = QColor::fromHsl(color->c1, color->c2, color->c3, color->c4);
441                 theImg.setPixel(px, py, C.rgba());
442                 break;
443             }
444 #endif
445             case GdalAdapter::Palette_CMYK:
446             {
447                 float* ix = (lineBuf + (col*bandCount));
448                 const GDALColorEntry* color = colTable->GetColorEntry(*ix);
449                 QColor C = QColor::fromCmyk(color->c1, color->c2, color->c3, color->c4);
450                 theImg.setPixel(px, py, C.rgba());
451                 break;
452             }
453             }
454         }
455         QCoreApplication::processEvents();
456     }
457 
458     img.theFilename = fn;
459     img.theImg = QPixmap::fromImage(theImg);
460     theImages.push_back(img);
461     theBbox = theBbox.united(bbox);
462 
463     GDALClose((GDALDatasetH)poDataset);
464     return true;
465 }
466 
onLoadImage()467 void GdalAdapter::onLoadImage()
468 {
469     int fileOk = 0;
470 
471     QStringList fileNames = QFileDialog::getOpenFileNames(
472                     NULL,
473                     tr("Open GDAL files"),
474                     "", FILTER_OPEN_SUPPORTED);
475     if (fileNames.isEmpty())
476         return;
477 
478 //    theBbox = QRectF();
479 //    theImages.clear();
480 
481     for (int i=0; i<fileNames.size(); i++) {
482         if (loadImage(fileNames[i]))
483             ++fileOk;
484     }
485 
486     if (!fileOk) {
487         QMessageBox::critical(0,QCoreApplication::translate("GdalBackground","No valid file"),QCoreApplication::translate("GdalBackground","No valid GDAL file could be found."));
488     } else {
489         emit forceZoom();
490         emit forceRefresh();
491     }
492 
493     return;
494 }
495 
onSetSourceTag()496 void GdalAdapter::onSetSourceTag()
497 {
498     bool ok;
499     QString text = QInputDialog::getText(0, tr("Please specify automatic \"source\" tag value"),
500                                          tr("Value:"), QLineEdit::Normal, theSourceTag, &ok);
501     if (ok)
502         theSourceTag = text;
503 }
504 
getSourceTag() const505 QString GdalAdapter::getSourceTag() const
506 {
507     return theSourceTag;
508 }
509 
getHost() const510 QString	GdalAdapter::getHost() const
511 {
512     return QString();
513 }
514 
getType() const515 IMapAdapter::Type GdalAdapter::getType() const
516 {
517     return IMapAdapter::DirectBackground;
518 }
519 
getMenu() const520 QMenu* GdalAdapter::getMenu() const
521 {
522     return theMenu;
523 }
524 
getBoundingbox() const525 QRectF GdalAdapter::getBoundingbox() const
526 {
527     QRectF projBbox = theBbox;
528     if (isLatLon)
529         projBbox = QRectF(angToRad(theBbox.left()), angToRad(theBbox.top()), angToRad(theBbox.width()), angToRad(theBbox.height()));
530     return projBbox;
531 }
532 
projection() const533 QString GdalAdapter::projection() const
534 {
535     return theProjection;
536 }
537 
getPixmap(const QRectF &,const QRectF & theProjBbox,const QRect & src) const538 QPixmap GdalAdapter::getPixmap(const QRectF& /*wgs84Bbox*/, const QRectF& theProjBbox, const QRect& src) const
539 {
540     QPixmap pix(src.size());
541     pix.fill(Qt::transparent);
542     QPainter p(&pix);
543 
544     QRectF projBbox = theProjBbox;
545     if (isLatLon)
546         projBbox = QRectF(radToAng(theProjBbox.left()), radToAng(theProjBbox.top()), radToAng(theProjBbox.width()), radToAng(theProjBbox.height()));
547 
548     for (int i=0; i<theImages.size(); ++i) {
549         QPixmap theImg = theImages[i].theImg;
550 
551         QSizeF sz(projBbox.width() / theImages[i].adfGeoTransform[1], projBbox.height() / theImages[i].adfGeoTransform[5]);
552         if (sz.isNull())
553             return QPixmap();
554 
555         QPointF s((projBbox.left() - theImages[i].adfGeoTransform[0]) / theImages[i].adfGeoTransform[1],
556                  (projBbox.top() - theImages[i].adfGeoTransform[3]) / theImages[i].adfGeoTransform[5]);
557 
558         qDebug() << "Pixmap Origin: " << s.x() << "," << s.y();
559         qDebug() << "Pixmap size: " << sz.width() << "," << sz.height();
560 
561         double rtx = src.width() / (double)sz.width();
562         double rty = src.height() / (double)sz.height();
563 
564         QRect mRect = QRect(s.toPoint(), sz.toSize());
565         QRect iRect = theImg.rect().intersected(mRect);
566         QRect sRect = QRect(iRect.topLeft() - mRect.topLeft(), iRect.size());
567         QRect fRect = QRect(sRect.x() * rtx, sRect.y() * rty, sRect.width() * rtx, sRect.height() * rty);
568 
569         qDebug() << "mrect: " << mRect;
570         qDebug() << "iRect: " << iRect;
571         qDebug() << "sRect: " << sRect;
572 
573     //	QImage img2 = theImg.copy(iRect).scaled(fRect.size());
574     //	p.drawImage(fRect.topLeft(), img2);
575         QPixmap img2 = theImg.copy(iRect).scaled(fRect.size());
576         p.drawPixmap(fRect.topLeft(), img2);
577     }
578 
579     p.end();
580     return pix;
581 }
582 
getImageManager()583 IImageManager* GdalAdapter::getImageManager()
584 {
585     return NULL;
586 }
587 
setImageManager(IImageManager *)588 void GdalAdapter::setImageManager(IImageManager* /*anImageManager*/)
589 {
590 }
591 
cleanup()592 void GdalAdapter::cleanup()
593 {
594     theImages.clear();
595     theBbox = QRectF();
596     theProjection = QString();
597 }
598 
toXML(QXmlStreamWriter & stream)599 bool GdalAdapter::toXML(QXmlStreamWriter& stream)
600 {
601     bool OK = true;
602 
603     stream.writeStartElement("Images");
604 
605     stream.writeAttribute("projection", theProjection);
606     if (!theSourceTag.isEmpty())
607         stream.writeAttribute("source", theSourceTag);
608     for (int i=0; i<theImages.size(); ++i) {
609         stream.writeStartElement("Image");
610         stream.writeAttribute("filename", theImages[i].theFilename);
611         stream.writeEndElement();
612     }
613 
614     stream.writeEndElement();
615 
616     return OK;
617 }
618 
fromXML(QXmlStreamReader & stream)619 void GdalAdapter::fromXML(QXmlStreamReader& stream)
620 {
621     theBbox = QRectF();
622     theImages.clear();
623 
624     while(!stream.atEnd() && !stream.isEndElement()) {
625         if (stream.name() == "Images") {
626             if (stream.attributes().hasAttribute("projection"))
627                 theProjection = stream.attributes().value("projection").toString();
628             if (stream.attributes().hasAttribute("source"))
629                 theSourceTag = stream.attributes().value("source").toString();
630             stream.readNext();
631             while(!stream.atEnd() && !stream.isEndElement()) {
632                 if (stream.name() == "Image") {
633                     QString fn = stream.attributes().value("filename").toString();
634                     if (!fn.isEmpty())
635                         loadImage(fn);
636                     stream.readNext();
637                 } else if (!stream.isWhitespace()) {
638                     qDebug() << "gdalimage: logic error: " << stream.name() << " : " << stream.tokenType() << " (" << stream.lineNumber() << ")";
639                     stream.skipCurrentElement();
640                 }
641                 stream.readNext();
642             }
643         } else if (!stream.isWhitespace()) {
644             qDebug() << "gdalmain: logic error: " << stream.name() << " : " << stream.tokenType() << " (" << stream.lineNumber() << ")";
645             stream.skipCurrentElement();
646         }
647 
648         stream.readNext();
649     }
650 }
651 
toPropertiesHtml()652 QString GdalAdapter::toPropertiesHtml()
653 {
654     QString h;
655 
656     QStringList fn;
657     for (int i=0; i<theImages.size(); ++i) {
658         fn << QDir::toNativeSeparators(theImages[i].theFilename);
659     }
660     h += "<i>" + tr("Filename(s)") + ": </i>" + fn.join("; ");
661 
662     return h;
663 }
664 
665 #if !(QT_VERSION >= QT_VERSION_CHECK(5,0,0))
666 Q_EXPORT_PLUGIN2(MGdalBackgroundPlugin, GdalAdapterFactory)
667 #endif
668