QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsmaprenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaprender.cpp - class for rendering map layer set
3  ----------------------
4  begin : January 2006
5  copyright : (C) 2006 by Martin Dobias
6  email : wonder.sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <cmath>
17 #include <cfloat>
18 
19 #include "qgscoordinatetransform.h"
20 #include "qgscrscache.h"
21 #include "qgslogger.h"
22 #include "qgsmessagelog.h"
23 #include "qgsmaprenderer.h"
24 #include "qgsscalecalculator.h"
25 #include "qgsmaptopixel.h"
26 #include "qgsmaplayer.h"
27 #include "qgsmaplayerregistry.h"
28 #include "qgsmapsettings.h"
29 #include "qgsdistancearea.h"
30 #include "qgsproject.h"
31 #include "qgsvectorlayer.h"
32 
33 #include <QDomDocument>
34 #include <QDomNode>
35 #include <QMutexLocker>
36 #include <QPainter>
37 #include <QListIterator>
38 #include <QSettings>
39 #include <QTime>
40 #include <QCoreApplication>
41 
43 {
44  mScale = 1.0;
47 
48  mDrawing = false;
49  mOverview = false;
50 
51  // set default map units - we use WGS 84 thus use degrees
53 
54  mSize = QSize( 0, 0 );
55 
56  mProjectionsEnabled = false;
58 
60 
61  mLabelingEngine = NULL;
62 }
63 
65 {
66  delete mScaleCalculator;
67  delete mDistArea;
68  delete mDestCRS;
69  delete mLabelingEngine;
70 }
71 
73 {
74  return mExtent;
75 }
76 
78 {
80 }
81 
83 {
84  //remember the previous extent
86 
87  // Don't allow zooms where the current extent is so small that it
88  // can't be accurately represented using a double (which is what
89  // currentExtent uses). Excluding 0 avoids a divide by zero and an
90  // infinite loop when rendering to a new canvas. Excluding extents
91  // greater than 1 avoids doing unnecessary calculations.
92 
93  // The scheme is to compare the width against the mean x coordinate
94  // (and height against mean y coordinate) and only allow zooms where
95  // the ratio indicates that there is more than about 12 significant
96  // figures (there are about 16 significant figures in a double).
97 
98  if ( extent.width() > 0 &&
99  extent.height() > 0 &&
100  extent.width() < 1 &&
101  extent.height() < 1 )
102  {
103  // Use abs() on the extent to avoid the case where the extent is
104  // symmetrical about 0.
105  double xMean = ( qAbs( extent.xMinimum() ) + qAbs( extent.xMaximum() ) ) * 0.5;
106  double yMean = ( qAbs( extent.yMinimum() ) + qAbs( extent.yMaximum() ) ) * 0.5;
107 
108  double xRange = extent.width() / xMean;
109  double yRange = extent.height() / yMean;
110 
111  static const double minProportion = 1e-12;
112  if ( xRange < minProportion || yRange < minProportion )
113  return false;
114  }
115 
116  mExtent = extent;
117  if ( !extent.isEmpty() )
119 
120  emit extentsChanged();
121  return true;
122 }
123 
124 
125 
126 void QgsMapRenderer::setOutputSize( QSize size, int dpi )
127 {
128  mSize = QSizeF( size.width(), size.height() );
129  mScaleCalculator->setDpi( dpi );
131 }
132 
133 void QgsMapRenderer::setOutputSize( QSizeF size, double dpi )
134 {
135  mSize = size;
136  mScaleCalculator->setDpi( dpi );
138 }
139 
141 {
142  return mScaleCalculator->dpi();
143 }
144 
146 {
147  return mSize.toSize();
148 }
149 
151 {
152  return mSize;
153 }
154 
156 {
157  double myHeight = mSize.height();
158  double myWidth = mSize.width();
159 
160  QgsMapToPixel newCoordXForm;
161 
162  if ( !myWidth || !myHeight )
163  {
164  mScale = 1.0;
165  newCoordXForm.setParameters( 0, 0, 0, 0 );
166  return;
167  }
168 
169  // calculate the translation and scaling parameters
170  // mapUnitsPerPixel = map units per pixel
171  double mapUnitsPerPixelY = mExtent.height() / myHeight;
172  double mapUnitsPerPixelX = mExtent.width() / myWidth;
173  mMapUnitsPerPixel = mapUnitsPerPixelY > mapUnitsPerPixelX ? mapUnitsPerPixelY : mapUnitsPerPixelX;
174 
175  // calculate the actual extent of the mapCanvas
176  double dxmin, dxmax, dymin, dymax, whitespace;
177 
178  if ( mapUnitsPerPixelY > mapUnitsPerPixelX )
179  {
180  dymin = mExtent.yMinimum();
181  dymax = mExtent.yMaximum();
182  whitespace = (( myWidth * mMapUnitsPerPixel ) - mExtent.width() ) * 0.5;
183  dxmin = mExtent.xMinimum() - whitespace;
184  dxmax = mExtent.xMaximum() + whitespace;
185  }
186  else
187  {
188  dxmin = mExtent.xMinimum();
189  dxmax = mExtent.xMaximum();
190  whitespace = (( myHeight * mMapUnitsPerPixel ) - mExtent.height() ) * 0.5;
191  dymin = mExtent.yMinimum() - whitespace;
192  dymax = mExtent.yMaximum() + whitespace;
193  }
194 
195  QgsDebugMsg( QString( "Map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mapUnitsPerPixelX ) ).arg( qgsDoubleToString( mapUnitsPerPixelY ) ) );
196  QgsDebugMsg( QString( "Pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( myWidth ) ).arg( qgsDoubleToString( myHeight ) ) );
197  QgsDebugMsg( QString( "Extent dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() ) ).arg( qgsDoubleToString( mExtent.height() ) ) );
199 
200  // update extent
201  mExtent.setXMinimum( dxmin );
202  mExtent.setXMaximum( dxmax );
203  mExtent.setYMinimum( dymin );
204  mExtent.setYMaximum( dymax );
205 
206  QgsDebugMsg( QString( "Adjusted map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() / myWidth ) ).arg( qgsDoubleToString( mExtent.height() / myHeight ) ) );
207 
208  QgsDebugMsg( QString( "Recalced pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() / mMapUnitsPerPixel ) ).arg( qgsDoubleToString( mExtent.height() / mMapUnitsPerPixel ) ) );
209 
210  // update the scale
211  updateScale();
212 
213  QgsDebugMsg( QString( "Scale (assuming meters as map units) = 1:%1" ).arg( qgsDoubleToString( mScale ) ) );
214 
215  newCoordXForm.setParameters( mMapUnitsPerPixel, dxmin, dymin, myHeight );
216  mRenderContext.setMapToPixel( newCoordXForm );
218 }
219 
220 
221 void QgsMapRenderer::render( QPainter* painter, double* forceWidthScale )
222 {
223  //Lock render method for concurrent threads (e.g. from globe)
224  QMutexLocker renderLock( &mRenderMutex );
225 
226  QgsDebugMsg( "========== Rendering ==========" );
227 
228  if ( mExtent.isEmpty() )
229  {
230  QgsDebugMsg( "empty extent... not rendering" );
231  return;
232  }
233 
234  if ( mSize.width() == 1 && mSize.height() == 1 )
235  {
236  QgsDebugMsg( "size 1x1... not rendering" );
237  return;
238  }
239 
240  QPaintDevice* thePaintDevice = painter->device();
241  if ( !thePaintDevice )
242  {
243  return;
244  }
245 
246  // wait
247  if ( mDrawing )
248  {
249  QgsDebugMsg( "already rendering" );
250  QCoreApplication::processEvents();
251  }
252 
253  if ( mDrawing )
254  {
255  QgsDebugMsg( "still rendering - skipping" );
256  return;
257  }
258 
259  mDrawing = true;
260 
261  const QgsCoordinateTransform *ct;
262 
263 #ifdef QGISDEBUG
264  QgsDebugMsg( "Starting to render layer stack." );
265  QTime renderTime;
266  renderTime.start();
267 #endif
268 
269  if ( mOverview )
271 
272  mRenderContext.setPainter( painter );
274  //this flag is only for stopping during the current rendering progress,
275  //so must be false at every new render operation
277 
278  // set selection color
280  int myRed = prj->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
281  int myGreen = prj->readNumEntry( "Gui", "/SelectionColorGreenPart", 255 );
282  int myBlue = prj->readNumEntry( "Gui", "/SelectionColorBluePart", 0 );
283  int myAlpha = prj->readNumEntry( "Gui", "/SelectionColorAlphaPart", 255 );
284  mRenderContext.setSelectionColor( QColor( myRed, myGreen, myBlue, myAlpha ) );
285 
286  //calculate scale factor
287  //use the specified dpi and not those from the paint device
288  //because sometimes QPainter units are in a local coord sys (e.g. in case of QGraphicsScene)
289  double sceneDpi = mScaleCalculator->dpi();
290  double scaleFactor = 1.0;
292  {
293  if ( forceWidthScale )
294  {
295  scaleFactor = *forceWidthScale;
296  }
297  else
298  {
299  scaleFactor = sceneDpi / 25.4;
300  }
301  }
302  double rasterScaleFactor = ( thePaintDevice->logicalDpiX() + thePaintDevice->logicalDpiY() ) / 2.0 / sceneDpi;
304  {
305  mRenderContext.setRasterScaleFactor( rasterScaleFactor );
306  }
307  if ( mRenderContext.scaleFactor() != scaleFactor )
308  {
309  mRenderContext.setScaleFactor( scaleFactor );
310  }
312  {
313  //add map scale to render context
315  }
316  if ( mLastExtent != mExtent )
317  {
319  }
320 
322  if ( mLabelingEngine )
324 
325  // render all layers in the stack, starting at the base
326  QListIterator<QString> li( mLayerSet );
327  li.toBack();
328 
329  QgsRectangle r1, r2;
330 
331  while ( li.hasPrevious() )
332  {
334  {
335  break;
336  }
337 
338  // Store the painter in case we need to swap it out for the
339  // cache painter
340  QPainter * mypContextPainter = mRenderContext.painter();
341  // Flattened image for drawing when a blending mode is set
342  QImage * mypFlattenedImage = 0;
343 
344  QString layerId = li.previous();
345 
346  QgsDebugMsg( "Rendering at layer item " + layerId );
347 
348  // This call is supposed to cause the progress bar to
349  // advance. However, it seems that updating the progress bar is
350  // incompatible with having a QPainter active (the one that is
351  // passed into this function), as Qt produces a number of errors
352  // when try to do so. I'm (Gavin) not sure how to fix this, but
353  // added these comments and debug statement to help others...
354  QgsDebugMsg( "If there is a QPaintEngine error here, it is caused by an emit call" );
355 
356  //emit drawingProgress(myRenderCounter++, mLayerSet.size());
358 
359  if ( !ml )
360  {
361  QgsDebugMsg( "Layer not found in registry!" );
362  continue;
363  }
364 
365  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 extent:%5 blendmode:%6" )
366  .arg( ml->name() )
367  .arg( ml->minimumScale() )
368  .arg( ml->maximumScale() )
369  .arg( ml->hasScaleBasedVisibility() )
370  .arg( ml->extent().toString() )
371  .arg( ml->blendMode() )
372  );
373 
375  {
376  // Set the QPainter composition mode so that this layer is rendered using
377  // the desired blending mode
378  mypContextPainter->setCompositionMode( ml->blendMode() );
379  }
380 
381  if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() <= mScale && mScale < ml->maximumScale() ) || mOverview )
382  {
383  connect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );
384 
385  //
386  // Now do the call to the layer that actually does
387  // the rendering work!
388  //
389 
390  bool split = false;
391 
392  if ( hasCrsTransformEnabled() )
393  {
394  r1 = mExtent;
395  split = splitLayersExtent( ml, r1, r2 );
396  ct = transformation( ml );
398  QgsDebugMsg( " extent 1: " + r1.toString() );
399  QgsDebugMsg( " extent 2: " + r2.toString() );
400  if ( !r1.isFinite() || !r2.isFinite() ) //there was a problem transforming the extent. Skip the layer
401  {
402  continue;
403  }
404  }
405  else
406  {
407  ct = NULL;
408  }
409 
411 
412  //decide if we have to scale the raster
413  //this is necessary in case QGraphicsScene is used
414  bool scaleRaster = false;
415  QgsMapToPixel rasterMapToPixel;
416  QgsMapToPixel bk_mapToPixel;
417 
418  if ( ml->type() == QgsMapLayer::RasterLayer && qAbs( rasterScaleFactor - 1.0 ) > 0.000001 )
419  {
420  scaleRaster = true;
421  }
422 
423  QSettings mySettings;
424 
425  // If we are drawing with an alternative blending mode then we need to render to a separate image
426  // before compositing this on the map. This effectively flattens the layer and prevents
427  // blending occuring between objects on the layer
428  // (this is not required for raster layers or when layer caching is enabled, since that has the same effect)
429  bool flattenedLayer = false;
431  {
432  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
433  if ((( vl->blendMode() != QPainter::CompositionMode_SourceOver )
434  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
435  || ( vl->layerTransparency() != 0 ) ) )
436  {
437  flattenedLayer = true;
438  mypFlattenedImage = new QImage( mRenderContext.painter()->device()->width(),
439  mRenderContext.painter()->device()->height(), QImage::Format_ARGB32 );
440  if ( mypFlattenedImage->isNull() )
441  {
442  QgsDebugMsg( "insufficient memory for image " + QString::number( mRenderContext.painter()->device()->width() ) + "x" + QString::number( mRenderContext.painter()->device()->height() ) );
443  emit drawError( ml );
444  painter->end(); // drawError is not caught by anyone, so we end painting to notify caller
445  return;
446  }
447  mypFlattenedImage->fill( 0 );
448  QPainter * mypPainter = new QPainter( mypFlattenedImage );
449  if ( mySettings.value( "/qgis/enable_anti_aliasing", true ).toBool() )
450  {
451  mypPainter->setRenderHint( QPainter::Antialiasing );
452  }
453  mypPainter->scale( rasterScaleFactor, rasterScaleFactor );
454  mRenderContext.setPainter( mypPainter );
455  }
456  }
457 
458  // Per feature blending mode
460  {
461  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
462  if ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
463  {
464  // set the painter to the feature blend mode, so that features drawn
465  // on this layer will interact and blend with each other
466  mRenderContext.painter()->setCompositionMode( vl->featureBlendMode() );
467  }
468  }
469 
470  if ( scaleRaster )
471  {
472  bk_mapToPixel = mRenderContext.mapToPixel();
473  rasterMapToPixel = mRenderContext.mapToPixel();
475  rasterMapToPixel.setYMaximum( mSize.height() * rasterScaleFactor );
476  mRenderContext.setMapToPixel( rasterMapToPixel );
477  mRenderContext.painter()->save();
478  mRenderContext.painter()->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
479  }
480 
481  if ( !ml->draw( mRenderContext ) )
482  {
483  emit drawError( ml );
484  }
485  else
486  {
487  QgsDebugMsg( "Layer rendered without issues" );
488  }
489 
490  if ( split )
491  {
493  if ( !ml->draw( mRenderContext ) )
494  {
495  emit drawError( ml );
496  }
497  }
498 
499  if ( scaleRaster )
500  {
501  mRenderContext.setMapToPixel( bk_mapToPixel );
502  mRenderContext.painter()->restore();
503  }
504 
505  //apply layer transparency for vector layers
507  {
508  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
509  if ( vl->layerTransparency() != 0 )
510  {
511  // a layer transparency has been set, so update the alpha for the flattened layer
512  // by combining it with the layer transparency
513  QColor transparentFillColor = QColor( 0, 0, 0, 255 - ( 255 * vl->layerTransparency() / 100 ) );
514  // use destination in composition mode to merge source's alpha with destination
515  mRenderContext.painter()->setCompositionMode( QPainter::CompositionMode_DestinationIn );
516  mRenderContext.painter()->fillRect( 0, 0, mRenderContext.painter()->device()->width(),
517  mRenderContext.painter()->device()->height(), transparentFillColor );
518  }
519  }
520 
521  if ( flattenedLayer )
522  {
523  // If we flattened this layer for alternate blend modes, composite it now
524  delete mRenderContext.painter();
525  mRenderContext.setPainter( mypContextPainter );
526  mypContextPainter->save();
527  mypContextPainter->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
528  mypContextPainter->drawImage( 0, 0, *( mypFlattenedImage ) );
529  mypContextPainter->restore();
530  delete mypFlattenedImage;
531  mypFlattenedImage = 0;
532  }
533 
534  disconnect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );
535  }
536  else // layer not visible due to scale
537  {
538  QgsDebugMsg( "Layer not rendered because it is not within the defined "
539  "visibility scale range" );
540  }
541 
542  } // while (li.hasPrevious())
543 
544  QgsDebugMsg( "Done rendering map layers" );
545 
546  // Reset the composition mode before rendering the labels
547  mRenderContext.painter()->setCompositionMode( QPainter::CompositionMode_SourceOver );
548 
549  if ( !mOverview )
550  {
551  // render all labels for vector layers in the stack, starting at the base
552  li.toBack();
553  while ( li.hasPrevious() )
554  {
556  {
557  break;
558  }
559 
560  QString layerId = li.previous();
561 
562  // TODO: emit drawingProgress((myRenderCounter++),zOrder.size());
564 
565  if ( ml && ( ml->type() != QgsMapLayer::RasterLayer ) )
566  {
567  // only make labels if the layer is visible
568  // after scale dep viewing settings are checked
569  if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() < mScale && mScale < ml->maximumScale() ) )
570  {
571  bool split = false;
572 
573  if ( hasCrsTransformEnabled() )
574  {
575  QgsRectangle r1 = mExtent;
576  split = splitLayersExtent( ml, r1, r2 );
577  ct = transformation( ml );
579  }
580  else
581  {
582  ct = NULL;
583  }
584 
586 
587  ml->drawLabels( mRenderContext );
588  if ( split )
589  {
591  ml->drawLabels( mRenderContext );
592  }
593  }
594  }
595  }
596  } // if (!mOverview)
597 
598  // make sure progress bar arrives at 100%!
599  emit drawingProgress( 1, 1 );
600 
601  if ( mLabelingEngine )
602  {
603  // set correct extent
606 
609  }
610 
611  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
612 
613  mDrawing = false;
614 }
615 
617 {
619 
620  // Since the map units have changed, force a recalculation of the scale.
621  updateScale();
622 
623  emit mapUnitsChanged();
624 }
625 
627 {
628  return mScaleCalculator->mapUnits();
629 }
630 
631 void QgsMapRenderer::onDrawingProgress( int current, int total )
632 {
633  Q_UNUSED( current );
634  Q_UNUSED( total );
635 }
636 
638 {
639  if ( mProjectionsEnabled != enabled )
640  {
641  mProjectionsEnabled = enabled;
642  QgsDebugMsg( "Adjusting DistArea projection on/off" );
643  mDistArea->setEllipsoidalMode( enabled );
646 
648  emit hasCrsTransformEnabled( enabled ); // deprecated
650 
651  emit hasCrsTransformEnabledChanged( enabled );
652  }
653 }
654 
656 {
657  return mProjectionsEnabled;
658 }
659 
660 void QgsMapRenderer::setDestinationCrs( const QgsCoordinateReferenceSystem& crs, bool refreshCoordinateTransformInfo, bool transformExtent )
661 {
662  QgsDebugMsg( "* Setting destCRS : = " + crs.toProj4() );
663  QgsDebugMsg( "* DestCRS.srsid() = " + QString::number( crs.srsid() ) );
664  if ( *mDestCRS != crs )
665  {
666  if ( refreshCoordinateTransformInfo )
667  {
669  }
670  QgsRectangle rect;
671  if ( transformExtent && !mExtent.isEmpty() )
672  {
673  QgsCoordinateTransform transform( *mDestCRS, crs );
674  rect = transform.transformBoundingBox( mExtent );
675  }
676 
677  QgsDebugMsg( "Setting DistArea CRS to " + QString::number( crs.srsid() ) );
678  mDistArea->setSourceCrs( crs.srsid() );
679  *mDestCRS = crs;
681 
682  if ( !rect.isEmpty() )
683  {
684  setExtent( rect );
685  }
686 
687  emit destinationSrsChanged();
688  }
689 }
690 
692 {
693  QgsDebugMsgLevel( "* Returning destCRS", 3 );
694  QgsDebugMsgLevel( "* DestCRS.srsid() = " + QString::number( mDestCRS->srsid() ), 3 );
695  QgsDebugMsgLevel( "* DestCRS.proj4() = " + mDestCRS->toProj4(), 3 );
696  return *mDestCRS;
697 }
698 
699 
701 {
702  bool split = false;
703 
704  if ( hasCrsTransformEnabled() )
705  {
706  try
707  {
708 #ifdef QGISDEBUG
709  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
710 #endif
711  // Split the extent into two if the source CRS is
712  // geographic and the extent crosses the split in
713  // geographic coordinates (usually +/- 180 degrees,
714  // and is assumed to be so here), and draw each
715  // extent separately.
716  static const double splitCoord = 180.0;
717 
718  const QgsCoordinateTransform *transform = transformation( layer );
719  if ( layer->crs().geographicFlag() )
720  {
721  // Note: ll = lower left point
722  // and ur = upper right point
723 
724  QgsPoint ll( extent.xMinimum(), extent.yMinimum() );
725  QgsPoint ur( extent.xMaximum(), extent.yMaximum() );
726 
727  if ( transform )
728  {
729  ll = transform->transform( ll.x(), ll.y(),
731  ur = transform->transform( ur.x(), ur.y(),
733  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
734  }
735 
736  if ( ll.x() > ur.x() )
737  {
738  r2 = extent;
739  extent.setXMinimum( splitCoord );
740  r2.setXMaximum( splitCoord );
741  split = true;
742  }
743  }
744  else // can't cross 180
745  {
746  if ( transform )
747  {
748  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
749  }
750  }
751  }
752  catch ( QgsCsException &cse )
753  {
754  Q_UNUSED( cse );
755  QgsDebugMsg( "Transform error caught" );
756  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
757  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
758  }
759  }
760  return split;
761 }
762 
764 {
765  //QgsDebugMsg( QString( "sourceCrs = " + tr( theLayer )->sourceCrs().authid() ) );
766  //QgsDebugMsg( QString( "destCRS = " + tr( theLayer )->destCRS().authid() ) );
767  //QgsDebugMsg( QString( "extent = " + extent.toString() ) );
768  if ( hasCrsTransformEnabled() )
769  {
770  try
771  {
772  const QgsCoordinateTransform *transform = transformation( theLayer );
773  if ( transform )
774  {
775  extent = transform->transformBoundingBox( extent );
776  }
777  }
778  catch ( QgsCsException &cse )
779  {
780  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
781  }
782  }
783 
784  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
785 
786  return extent;
787 }
788 
790 {
791 #if QGISDEBUG
792  const QgsCoordinateTransform *transform = transformation( theLayer );
793  QgsDebugMsg( QString( "layer sourceCrs = " + ( transform ? transform->sourceCrs().authid() : "none" ) ) );
794  QgsDebugMsg( QString( "layer destCRS = " + ( transform ? transform->destCRS().authid() : "none" ) ) );
795  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
796 #endif
797  if ( hasCrsTransformEnabled() )
798  {
799  try
800  {
801  const QgsCoordinateTransform *transform = transformation( theLayer );
802  if ( transform )
803  {
804  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
805  }
806  }
807  catch ( QgsCsException &cse )
808  {
809  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
810  }
811  }
812 
813  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
814 
815  return extent;
816 }
817 
819 {
820  if ( hasCrsTransformEnabled() )
821  {
822  try
823  {
824  const QgsCoordinateTransform *transform = transformation( theLayer );
825  if ( transform )
826  {
827  point = transform->transform( point, QgsCoordinateTransform::ForwardTransform );
828  }
829  }
830  catch ( QgsCsException &cse )
831  {
832  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
833  }
834  }
835  else
836  {
837  // leave point without transformation
838  }
839  return point;
840 }
841 
843 {
844  if ( hasCrsTransformEnabled() )
845  {
846  try
847  {
848  const QgsCoordinateTransform *transform = transformation( theLayer );
849  if ( transform )
850  {
851  rect = transform->transform( rect, QgsCoordinateTransform::ForwardTransform );
852  }
853  }
854  catch ( QgsCsException &cse )
855  {
856  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
857  }
858  }
859  else
860  {
861  // leave point without transformation
862  }
863  return rect;
864 }
865 
867 {
868  if ( hasCrsTransformEnabled() )
869  {
870  try
871  {
872  const QgsCoordinateTransform *transform = transformation( theLayer );
873  if ( transform )
874  point = transform->transform( point, QgsCoordinateTransform::ReverseTransform );
875  }
876  catch ( QgsCsException &cse )
877  {
878  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
879  }
880  }
881  else
882  {
883  // leave point without transformation
884  }
885  return point;
886 }
887 
889 {
890  if ( hasCrsTransformEnabled() )
891  {
892  try
893  {
894  const QgsCoordinateTransform *transform = transformation( theLayer );
895  if ( transform )
896  rect = transform->transform( rect, QgsCoordinateTransform::ReverseTransform );
897  }
898  catch ( QgsCsException &cse )
899  {
900  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
901  }
902  }
903  return rect;
904 }
905 
906 
908 {
909  QgsDebugMsg( "called." );
911 
912  // reset the map canvas extent since the extent may now be smaller
913  // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction
915 
916  // iterate through the map layers and test each layers extent
917  // against the current min and max values
918  QStringList::iterator it = mLayerSet.begin();
919  QgsDebugMsg( QString( "Layer count: %1" ).arg( mLayerSet.count() ) );
920  while ( it != mLayerSet.end() )
921  {
922  QgsMapLayer * lyr = registry->mapLayer( *it );
923  if ( lyr == NULL )
924  {
925  QgsDebugMsg( QString( "WARNING: layer '%1' not found in map layer registry!" ).arg( *it ) );
926  }
927  else
928  {
929  QgsDebugMsg( "Updating extent using " + lyr->name() );
930  QgsDebugMsg( "Input extent: " + lyr->extent().toString() );
931 
932  if ( lyr->extent().isNull() )
933  {
934  ++it;
935  continue;
936  }
937 
938  // Layer extents are stored in the coordinate system (CS) of the
939  // layer. The extent must be projected to the canvas CS
941 
942  QgsDebugMsg( "Output extent: " + extent.toString() );
943  mFullExtent.unionRect( extent );
944 
945  }
946  ++it;
947  }
948 
949  if ( mFullExtent.width() == 0.0 || mFullExtent.height() == 0.0 )
950  {
951  // If all of the features are at the one point, buffer the
952  // rectangle a bit. If they are all at zero, do something a bit
953  // more crude.
954 
955  if ( mFullExtent.xMinimum() == 0.0 && mFullExtent.xMaximum() == 0.0 &&
956  mFullExtent.yMinimum() == 0.0 && mFullExtent.yMaximum() == 0.0 )
957  {
958  mFullExtent.set( -1.0, -1.0, 1.0, 1.0 );
959  }
960  else
961  {
962  const double padFactor = 1e-8;
963  double widthPad = mFullExtent.xMinimum() * padFactor;
964  double heightPad = mFullExtent.yMinimum() * padFactor;
965  double xmin = mFullExtent.xMinimum() - widthPad;
966  double xmax = mFullExtent.xMaximum() + widthPad;
967  double ymin = mFullExtent.yMinimum() - heightPad;
968  double ymax = mFullExtent.yMaximum() + heightPad;
969  mFullExtent.set( xmin, ymin, xmax, ymax );
970  }
971  }
972 
973  QgsDebugMsg( "Full extent: " + mFullExtent.toString() );
974 }
975 
977 {
979  return mFullExtent;
980 }
981 
982 void QgsMapRenderer::setLayerSet( const QStringList& layers )
983 {
984  QgsDebugMsg( QString( "Entering: %1" ).arg( layers.join( ", " ) ) );
985  mLayerSet = layers;
987 }
988 
990 {
991  return mLayerSet;
992 }
993 
994 
995 bool QgsMapRenderer::readXML( QDomNode & theNode )
996 {
997  QgsMapSettings tmpSettings;
998  tmpSettings.readXML( theNode );
999  //load coordinate transform into
1001  QDomElement layerCoordTransformInfoElem = theNode.firstChildElement( "layer_coordinate_transform_info" );
1002  if ( !layerCoordTransformInfoElem.isNull() )
1003  {
1004  QDomNodeList layerCoordinateTransformList = layerCoordTransformInfoElem.elementsByTagName( "layer_coordinate_transform" );
1005  QDomElement layerCoordTransformElem;
1006  for ( int i = 0; i < layerCoordinateTransformList.size(); ++i )
1007  {
1008  layerCoordTransformElem = layerCoordinateTransformList.at( i ).toElement();
1009  QString layerId = layerCoordTransformElem.attribute( "layerid" );
1010  if ( layerId.isEmpty() )
1011  {
1012  continue;
1013  }
1014 
1016  lct.srcAuthId = layerCoordTransformElem.attribute( "srcAuthId" );
1017  lct.destAuthId = layerCoordTransformElem.attribute( "destAuthId" );
1018  lct.srcDatumTransform = layerCoordTransformElem.attribute( "srcDatumTransform", "-1" ).toInt();
1019  lct.destDatumTransform = layerCoordTransformElem.attribute( "destDatumTransform", "-1" ).toInt();
1020  mLayerCoordinateTransformInfo.insert( layerId, lct );
1021  }
1022  }
1023 
1024 
1025  setMapUnits( tmpSettings.mapUnits() );
1026  setExtent( tmpSettings.extent() );
1028  setDestinationCrs( tmpSettings.destinationCrs() );
1029 
1030 
1031  return true;
1032 }
1033 
1034 bool QgsMapRenderer::writeXML( QDomNode & theNode, QDomDocument & theDoc )
1035 {
1036  QgsMapSettings tmpSettings;
1037  tmpSettings.setOutputDpi( outputDpi() );
1038  tmpSettings.setOutputSize( outputSize() );
1039  tmpSettings.setMapUnits( mapUnits() );
1040  tmpSettings.setExtent( extent() );
1042  tmpSettings.setDestinationCrs( destinationCrs() );
1043 
1044  tmpSettings.writeXML( theNode, theDoc );
1045  // layer coordinate transform infos
1046  QDomElement layerCoordTransformInfo = theDoc.createElement( "layer_coordinate_transform_info" );
1047  QHash< QString, QgsLayerCoordinateTransform >::const_iterator coordIt = mLayerCoordinateTransformInfo.constBegin();
1048  for ( ; coordIt != mLayerCoordinateTransformInfo.constEnd(); ++coordIt )
1049  {
1050  QDomElement layerCoordTransformElem = theDoc.createElement( "layer_coordinate_transform" );
1051  layerCoordTransformElem.setAttribute( "layerid", coordIt.key() );
1052  layerCoordTransformElem.setAttribute( "srcAuthId", coordIt->srcAuthId );
1053  layerCoordTransformElem.setAttribute( "destAuthId", coordIt->destAuthId );
1054  layerCoordTransformElem.setAttribute( "srcDatumTransform", QString::number( coordIt->srcDatumTransform ) );
1055  layerCoordTransformElem.setAttribute( "destDatumTransform", QString::number( coordIt->destDatumTransform ) );
1056  layerCoordTransformInfo.appendChild( layerCoordTransformElem );
1057  }
1058  theNode.appendChild( layerCoordTransformInfo );
1059  return true;
1060 }
1061 
1063 {
1064  if ( mLabelingEngine )
1065  delete mLabelingEngine;
1066 
1067  mLabelingEngine = iface;
1068 }
1069 
1071 {
1072  if ( !layer || !mDestCRS )
1073  {
1074  return 0;
1075  }
1076 
1077  if ( layer->crs().authid() == mDestCRS->authid() )
1078  {
1079  return 0;
1080  }
1081 
1082  QHash< QString, QgsLayerCoordinateTransform >::const_iterator ctIt = mLayerCoordinateTransformInfo.find( layer->id() );
1083  if ( ctIt != mLayerCoordinateTransformInfo.constEnd()
1084  && ctIt->srcAuthId == layer->crs().authid()
1085  && ctIt->destAuthId == mDestCRS->authid() )
1086  {
1087  return QgsCoordinateTransformCache::instance()->transform( ctIt->srcAuthId, ctIt->destAuthId, ctIt->srcDatumTransform, ctIt->destDatumTransform );
1088  }
1089  else
1090  {
1091  emit datumTransformInfoRequested( layer, layer->crs().authid(), mDestCRS->authid() );
1092  }
1093 
1094  //still not present? get coordinate transformation with -1/-1 datum transform as default
1095  ctIt = mLayerCoordinateTransformInfo.find( layer->id() );
1096  if ( ctIt == mLayerCoordinateTransformInfo.constEnd()
1097  || ctIt->srcAuthId == layer->crs().authid()
1098  || ctIt->destAuthId == mDestCRS->authid()
1099  )
1100  {
1101  return QgsCoordinateTransformCache::instance()->transform( layer->crs().authid(), mDestCRS->authid(), -1, -1 );
1102  }
1103  return QgsCoordinateTransformCache::instance()->transform( ctIt->srcAuthId, ctIt->destAuthId, ctIt->srcDatumTransform, ctIt->destDatumTransform );
1104 }
1105 
1108 QPainter::CompositionMode QgsMapRenderer::getCompositionMode( const QgsMapRenderer::BlendMode &blendMode )
1109 {
1110  // Map QgsMapRenderer::BlendNormal to QPainter::CompositionMode
1111  switch ( blendMode )
1112  {
1114  return QPainter::CompositionMode_SourceOver;
1116  return QPainter::CompositionMode_Lighten;
1118  return QPainter::CompositionMode_Screen;
1120  return QPainter::CompositionMode_ColorDodge;
1122  return QPainter::CompositionMode_Plus;
1124  return QPainter::CompositionMode_Darken;
1126  return QPainter::CompositionMode_Multiply;
1128  return QPainter::CompositionMode_ColorBurn;
1130  return QPainter::CompositionMode_Overlay;
1132  return QPainter::CompositionMode_SoftLight;
1134  return QPainter::CompositionMode_HardLight;
1136  return QPainter::CompositionMode_Difference;
1138  return QPainter::CompositionMode_Exclusion;
1139  default:
1140  return QPainter::CompositionMode_SourceOver;
1141  }
1142 }
1143 
1144 QgsMapRenderer::BlendMode QgsMapRenderer::getBlendModeEnum( const QPainter::CompositionMode &blendMode )
1145 {
1146  // Map QPainter::CompositionMode to QgsMapRenderer::BlendNormal
1147  switch ( blendMode )
1148  {
1149  case QPainter::CompositionMode_SourceOver:
1151  case QPainter::CompositionMode_Lighten:
1153  case QPainter::CompositionMode_Screen:
1155  case QPainter::CompositionMode_ColorDodge:
1157  case QPainter::CompositionMode_Plus:
1159  case QPainter::CompositionMode_Darken:
1161  case QPainter::CompositionMode_Multiply:
1163  case QPainter::CompositionMode_ColorBurn:
1165  case QPainter::CompositionMode_Overlay:
1167  case QPainter::CompositionMode_SoftLight:
1169  case QPainter::CompositionMode_HardLight:
1171  case QPainter::CompositionMode_Difference:
1173  case QPainter::CompositionMode_Exclusion:
1175  default:
1177  }
1178 }
1179 
1180 Q_GUI_EXPORT extern int qt_defaultDpiX();
1181 
1183 {
1184  // make sure the settings object is up-to-date
1192  return mMapSettings;
1193 }
1194 
1195 void QgsMapRenderer::addLayerCoordinateTransform( const QString& layerId, const QString& srcAuthId, const QString& destAuthId, int srcDatumTransform, int destDatumTransform )
1196 {
1198  lt.srcAuthId = srcAuthId;
1199  lt.destAuthId = destAuthId;
1200  lt.srcDatumTransform = srcDatumTransform;
1201  lt.destDatumTransform = destDatumTransform;
1202  mLayerCoordinateTransformInfo.insert( layerId, lt );
1203 }
1204 
1206 {
1208 }
1209 
1210 bool QgsMapRenderer::mDrawing = false;