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