QGIS API Documentation  2.0.1-Dufour
 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 "qgsdistancearea.h"
29 #include "qgsproject.h"
30 #include "qgsvectorlayer.h"
31 
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  return true;
120 }
121 
122 
123 
124 void QgsMapRenderer::setOutputSize( QSize size, int dpi )
125 {
126  mSize = QSizeF( size.width(), size.height() );
127  mScaleCalculator->setDpi( dpi );
129 }
130 
131 void QgsMapRenderer::setOutputSize( QSizeF size, double dpi )
132 {
133  mSize = size;
134  mScaleCalculator->setDpi( dpi );
136 }
137 
139 {
140  return mScaleCalculator->dpi();
141 }
142 
144 {
145  return mSize.toSize();
146 }
147 
149 {
150  return mSize;
151 }
152 
154 {
155  double myHeight = mSize.height();
156  double myWidth = mSize.width();
157 
158  QgsMapToPixel newCoordXForm;
159 
160  if ( !myWidth || !myHeight )
161  {
162  mScale = 1.0;
163  newCoordXForm.setParameters( 0, 0, 0, 0 );
164  return;
165  }
166 
167  // calculate the translation and scaling parameters
168  // mapUnitsPerPixel = map units per pixel
169  double mapUnitsPerPixelY = mExtent.height() / myHeight;
170  double mapUnitsPerPixelX = mExtent.width() / myWidth;
171  mMapUnitsPerPixel = mapUnitsPerPixelY > mapUnitsPerPixelX ? mapUnitsPerPixelY : mapUnitsPerPixelX;
172 
173  // calculate the actual extent of the mapCanvas
174  double dxmin, dxmax, dymin, dymax, whitespace;
175 
176  if ( mapUnitsPerPixelY > mapUnitsPerPixelX )
177  {
178  dymin = mExtent.yMinimum();
179  dymax = mExtent.yMaximum();
180  whitespace = (( myWidth * mMapUnitsPerPixel ) - mExtent.width() ) * 0.5;
181  dxmin = mExtent.xMinimum() - whitespace;
182  dxmax = mExtent.xMaximum() + whitespace;
183  }
184  else
185  {
186  dxmin = mExtent.xMinimum();
187  dxmax = mExtent.xMaximum();
188  whitespace = (( myHeight * mMapUnitsPerPixel ) - mExtent.height() ) * 0.5;
189  dymin = mExtent.yMinimum() - whitespace;
190  dymax = mExtent.yMaximum() + whitespace;
191  }
192 
193  QgsDebugMsg( QString( "Map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mapUnitsPerPixelX ) ).arg( qgsDoubleToString( mapUnitsPerPixelY ) ) );
194  QgsDebugMsg( QString( "Pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( myWidth ) ).arg( qgsDoubleToString( myHeight ) ) );
195  QgsDebugMsg( QString( "Extent dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() ) ).arg( qgsDoubleToString( mExtent.height() ) ) );
197 
198  // update extent
199  mExtent.setXMinimum( dxmin );
200  mExtent.setXMaximum( dxmax );
201  mExtent.setYMinimum( dymin );
202  mExtent.setYMaximum( dymax );
203 
204  QgsDebugMsg( QString( "Adjusted map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() / myWidth ) ).arg( qgsDoubleToString( mExtent.height() / myHeight ) ) );
205 
206  QgsDebugMsg( QString( "Recalced pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() / mMapUnitsPerPixel ) ).arg( qgsDoubleToString( mExtent.height() / mMapUnitsPerPixel ) ) );
207 
208  // update the scale
209  updateScale();
210 
211  QgsDebugMsg( QString( "Scale (assuming meters as map units) = 1:%1" ).arg( qgsDoubleToString( mScale ) ) );
212 
213  newCoordXForm.setParameters( mMapUnitsPerPixel, dxmin, dymin, myHeight );
214  mRenderContext.setMapToPixel( newCoordXForm );
216 }
217 
218 
219 void QgsMapRenderer::render( QPainter* painter, double* forceWidthScale )
220 {
221  //Lock render method for concurrent threads (e.g. from globe)
222  QMutexLocker renderLock( &mRenderMutex );
223 
224  //flag to see if the render context has changed
225  //since the last time we rendered. If it hasnt changed we can
226  //take some shortcuts with rendering
227  bool mySameAsLastFlag = true;
228 
229  QgsDebugMsg( "========== Rendering ==========" );
230 
231  if ( mExtent.isEmpty() )
232  {
233  QgsDebugMsg( "empty extent... not rendering" );
234  return;
235  }
236 
237  if ( mSize.width() == 1 && mSize.height() == 1 )
238  {
239  QgsDebugMsg( "size 1x1... not rendering" );
240  return;
241  }
242 
243  QPaintDevice* thePaintDevice = painter->device();
244  if ( !thePaintDevice )
245  {
246  return;
247  }
248 
249  // wait
250  if ( mDrawing )
251  {
252  QgsDebugMsg( "already rendering" );
253  QCoreApplication::processEvents();
254  }
255 
256  if ( mDrawing )
257  {
258  QgsDebugMsg( "still rendering - skipping" );
259  return;
260  }
261 
262  mDrawing = true;
263 
264  const QgsCoordinateTransform* ct;
265 
266 #ifdef QGISDEBUG
267  QgsDebugMsg( "Starting to render layer stack." );
268  QTime renderTime;
269  renderTime.start();
270 #endif
271 
272  if ( mOverview )
274 
275  mRenderContext.setPainter( painter );
277  //this flag is only for stopping during the current rendering progress,
278  //so must be false at every new render operation
280 
281  // set selection color
283  int myRed = prj->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
284  int myGreen = prj->readNumEntry( "Gui", "/SelectionColorGreenPart", 255 );
285  int myBlue = prj->readNumEntry( "Gui", "/SelectionColorBluePart", 0 );
286  int myAlpha = prj->readNumEntry( "Gui", "/SelectionColorAlphaPart", 255 );
287  mRenderContext.setSelectionColor( QColor( myRed, myGreen, myBlue, myAlpha ) );
288 
289  //calculate scale factor
290  //use the specified dpi and not those from the paint device
291  //because sometimes QPainter units are in a local coord sys (e.g. in case of QGraphicsScene)
292  double sceneDpi = mScaleCalculator->dpi();
293  double scaleFactor = 1.0;
295  {
296  if ( forceWidthScale )
297  {
298  scaleFactor = *forceWidthScale;
299  }
300  else
301  {
302  scaleFactor = sceneDpi / 25.4;
303  }
304  }
305  double rasterScaleFactor = ( thePaintDevice->logicalDpiX() + thePaintDevice->logicalDpiY() ) / 2.0 / sceneDpi;
307  {
308  mRenderContext.setRasterScaleFactor( rasterScaleFactor );
309  mySameAsLastFlag = false;
310  }
311  if ( mRenderContext.scaleFactor() != scaleFactor )
312  {
313  mRenderContext.setScaleFactor( scaleFactor );
314  mySameAsLastFlag = false;
315  }
317  {
318  //add map scale to render context
320  mySameAsLastFlag = false;
321  }
322  if ( mLastExtent != mExtent )
323  {
325  mySameAsLastFlag = false;
326  }
327 
329  if ( mLabelingEngine )
330  mLabelingEngine->init( this );
331 
332  // know we know if this render is just a repeat of the last time, we
333  // can clear caches if it has changed
334  if ( !mySameAsLastFlag )
335  {
336  //clear the cache pixmap if we changed resolution / extent
337  QSettings mySettings;
338  if ( mySettings.value( "/qgis/enable_render_caching", false ).toBool() )
339  {
341  }
342  }
343 
344  // render all layers in the stack, starting at the base
345  QListIterator<QString> li( mLayerSet );
346  li.toBack();
347 
348  QgsRectangle r1, r2;
349 
350  while ( li.hasPrevious() )
351  {
353  {
354  break;
355  }
356 
357  // Store the painter in case we need to swap it out for the
358  // cache painter
359  QPainter * mypContextPainter = mRenderContext.painter();
360  // Flattened image for drawing when a blending mode is set
361  QImage * mypFlattenedImage = 0;
362 
363  QString layerId = li.previous();
364 
365  QgsDebugMsg( "Rendering at layer item " + layerId );
366 
367  // This call is supposed to cause the progress bar to
368  // advance. However, it seems that updating the progress bar is
369  // incompatible with having a QPainter active (the one that is
370  // passed into this function), as Qt produces a number of errors
371  // when try to do so. I'm (Gavin) not sure how to fix this, but
372  // added these comments and debug statement to help others...
373  QgsDebugMsg( "If there is a QPaintEngine error here, it is caused by an emit call" );
374 
375  //emit drawingProgress(myRenderCounter++, mLayerSet.size());
377 
378  if ( !ml )
379  {
380  QgsDebugMsg( "Layer not found in registry!" );
381  continue;
382  }
383 
384  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 extent:%5 blendmode:%6" )
385  .arg( ml->name() )
386  .arg( ml->minimumScale() )
387  .arg( ml->maximumScale() )
388  .arg( ml->hasScaleBasedVisibility() )
389  .arg( ml->extent().toString() )
390  .arg( ml->blendMode() )
391  );
392 
394  {
395  // Set the QPainter composition mode so that this layer is rendered using
396  // the desired blending mode
397  mypContextPainter->setCompositionMode( ml->blendMode() );
398  }
399 
400  if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() <= mScale && mScale < ml->maximumScale() ) || mOverview )
401  {
402  connect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );
403 
404  //
405  // Now do the call to the layer that actually does
406  // the rendering work!
407  //
408 
409  bool split = false;
410 
411  if ( hasCrsTransformEnabled() )
412  {
413  r1 = mExtent;
414  split = splitLayersExtent( ml, r1, r2 );
417  QgsDebugMsg( " extent 1: " + r1.toString() );
418  QgsDebugMsg( " extent 2: " + r2.toString() );
419  if ( !r1.isFinite() || !r2.isFinite() ) //there was a problem transforming the extent. Skip the layer
420  {
421  continue;
422  }
423  }
424  else
425  {
426  ct = NULL;
427  }
428 
430 
431  //decide if we have to scale the raster
432  //this is necessary in case QGraphicsScene is used
433  bool scaleRaster = false;
434  QgsMapToPixel rasterMapToPixel;
435  QgsMapToPixel bk_mapToPixel;
436 
437  if ( ml->type() == QgsMapLayer::RasterLayer && qAbs( rasterScaleFactor - 1.0 ) > 0.000001 )
438  {
439  scaleRaster = true;
440  }
441 
442  // Force render of layers that are being edited
443  // or if there's a labeling engine that needs the layer to register features
444  if ( ml->type() == QgsMapLayer::VectorLayer )
445  {
446  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
447  if ( vl->isEditable() ||
449  {
450  ml->setCacheImage( 0 );
451  }
452  }
453 
454  QSettings mySettings;
455  bool useRenderCaching = false;
456  if ( ! split )//render caching does not yet cater for split extents
457  {
458  if ( mySettings.value( "/qgis/enable_render_caching", false ).toBool() )
459  {
460  useRenderCaching = true;
461  if ( !mySameAsLastFlag || ml->cacheImage() == 0 )
462  {
463  QgsDebugMsg( "Caching enabled but layer redraw forced by extent change or empty cache" );
464  QImage * mypImage = new QImage( mRenderContext.painter()->device()->width(),
465  mRenderContext.painter()->device()->height(), QImage::Format_ARGB32 );
466  if ( mypImage->isNull() )
467  {
468  QgsDebugMsg( "insufficient memory for image " + QString::number( mRenderContext.painter()->device()->width() ) + "x" + QString::number( mRenderContext.painter()->device()->height() ) );
469  emit drawError( ml );
470  painter->end(); // drawError is not caught by anyone, so we end painting to notify caller
471  return;
472  }
473  mypImage->fill( 0 );
474  ml->setCacheImage( mypImage ); //no need to delete the old one, maplayer does it for you
475  QPainter * mypPainter = new QPainter( ml->cacheImage() );
476  // Changed to enable anti aliasing by default in QGIS 1.7
477  if ( mySettings.value( "/qgis/enable_anti_aliasing", true ).toBool() )
478  {
479  mypPainter->setRenderHint( QPainter::Antialiasing );
480  }
481  mRenderContext.setPainter( mypPainter );
482  }
483  else if ( mySameAsLastFlag )
484  {
485  //draw from cached image
486  QgsDebugMsg( "Caching enabled --- drawing layer from cached image" );
487  mypContextPainter->drawImage( 0, 0, *( ml->cacheImage() ) );
488  disconnect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );
489  //short circuit as there is nothing else to do...
490  continue;
491  }
492  }
493  }
494 
495  // If we are drawing with an alternative blending mode then we need to render to a separate image
496  // before compositing this on the map. This effectively flattens the layer and prevents
497  // blending occuring between objects on the layer
498  // (this is not required for raster layers or when layer caching is enabled, since that has the same effect)
499  bool flattenedLayer = false;
501  {
502  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
503  if (( !useRenderCaching )
504  && (( vl->blendMode() != QPainter::CompositionMode_SourceOver )
505  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
506  || ( vl->layerTransparency() != 0 ) ) )
507  {
508  flattenedLayer = true;
509  mypFlattenedImage = new QImage( mRenderContext.painter()->device()->width(),
510  mRenderContext.painter()->device()->height(), QImage::Format_ARGB32 );
511  if ( mypFlattenedImage->isNull() )
512  {
513  QgsDebugMsg( "insufficient memory for image " + QString::number( mRenderContext.painter()->device()->width() ) + "x" + QString::number( mRenderContext.painter()->device()->height() ) );
514  emit drawError( ml );
515  painter->end(); // drawError is not caught by anyone, so we end painting to notify caller
516  return;
517  }
518  mypFlattenedImage->fill( 0 );
519  QPainter * mypPainter = new QPainter( mypFlattenedImage );
520  if ( mySettings.value( "/qgis/enable_anti_aliasing", true ).toBool() )
521  {
522  mypPainter->setRenderHint( QPainter::Antialiasing );
523  }
524  mypPainter->scale( rasterScaleFactor, rasterScaleFactor );
525  mRenderContext.setPainter( mypPainter );
526  }
527  }
528 
529  // Per feature blending mode
531  {
532  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
533  if ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
534  {
535  // set the painter to the feature blend mode, so that features drawn
536  // on this layer will interact and blend with each other
537  mRenderContext.painter()->setCompositionMode( vl->featureBlendMode() );
538  }
539  }
540 
541  if ( scaleRaster )
542  {
543  bk_mapToPixel = mRenderContext.mapToPixel();
544  rasterMapToPixel = mRenderContext.mapToPixel();
546  rasterMapToPixel.setYMaximum( mSize.height() * rasterScaleFactor );
547  mRenderContext.setMapToPixel( rasterMapToPixel );
548  mRenderContext.painter()->save();
549  mRenderContext.painter()->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
550  }
551 
552  if ( !ml->draw( mRenderContext ) )
553  {
554  emit drawError( ml );
555  }
556  else
557  {
558  QgsDebugMsg( "Layer rendered without issues" );
559  }
560 
561  if ( split )
562  {
564  if ( !ml->draw( mRenderContext ) )
565  {
566  emit drawError( ml );
567  }
568  }
569 
570  if ( scaleRaster )
571  {
572  mRenderContext.setMapToPixel( bk_mapToPixel );
573  mRenderContext.painter()->restore();
574  }
575 
576  //apply layer transparency for vector layers
578  {
579  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
580  if ( vl->layerTransparency() != 0 )
581  {
582  // a layer transparency has been set, so update the alpha for the flattened layer
583  // by combining it with the layer transparency
584  QColor transparentFillColor = QColor( 0, 0, 0, 255 - ( 255 * vl->layerTransparency() / 100 ) );
585  // use destination in composition mode to merge source's alpha with destination
586  mRenderContext.painter()->setCompositionMode( QPainter::CompositionMode_DestinationIn );
587  mRenderContext.painter()->fillRect( 0, 0, mRenderContext.painter()->device()->width(),
588  mRenderContext.painter()->device()->height(), transparentFillColor );
589  }
590  }
591 
592  if ( useRenderCaching )
593  {
594  // composite the cached image into our view and then clean up from caching
595  // by reinstating the painter as it was swapped out for caching renders
596  delete mRenderContext.painter();
597  mRenderContext.setPainter( mypContextPainter );
598  //draw from cached image that we created further up
599  if ( ml->cacheImage() )
600  mypContextPainter->drawImage( 0, 0, *( ml->cacheImage() ) );
601  }
602  else if ( flattenedLayer )
603  {
604  // If we flattened this layer for alternate blend modes, composite it now
605  delete mRenderContext.painter();
606  mRenderContext.setPainter( mypContextPainter );
607  mypContextPainter->save();
608  mypContextPainter->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
609  mypContextPainter->drawImage( 0, 0, *( mypFlattenedImage ) );
610  mypContextPainter->restore();
611  delete mypFlattenedImage;
612  mypFlattenedImage = 0;
613  }
614 
615  disconnect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );
616  }
617  else // layer not visible due to scale
618  {
619  QgsDebugMsg( "Layer not rendered because it is not within the defined "
620  "visibility scale range" );
621  }
622 
623  } // while (li.hasPrevious())
624 
625  QgsDebugMsg( "Done rendering map layers" );
626 
627  // Reset the composition mode before rendering the labels
628  mRenderContext.painter()->setCompositionMode( QPainter::CompositionMode_SourceOver );
629 
630  if ( !mOverview )
631  {
632  // render all labels for vector layers in the stack, starting at the base
633  li.toBack();
634  while ( li.hasPrevious() )
635  {
637  {
638  break;
639  }
640 
641  QString layerId = li.previous();
642 
643  // TODO: emit drawingProgress((myRenderCounter++),zOrder.size());
645 
646  if ( ml && ( ml->type() != QgsMapLayer::RasterLayer ) )
647  {
648  // only make labels if the layer is visible
649  // after scale dep viewing settings are checked
650  if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() < mScale && mScale < ml->maximumScale() ) )
651  {
652  bool split = false;
653 
654  if ( hasCrsTransformEnabled() )
655  {
656  QgsRectangle r1 = mExtent;
657  split = splitLayersExtent( ml, r1, r2 );
658  ct = new QgsCoordinateTransform( ml->crs(), *mDestCRS );
660  }
661  else
662  {
663  ct = NULL;
664  }
665 
667 
668  ml->drawLabels( mRenderContext );
669  if ( split )
670  {
672  ml->drawLabels( mRenderContext );
673  }
674  }
675  }
676  }
677  } // if (!mOverview)
678 
679  // make sure progress bar arrives at 100%!
680  emit drawingProgress( 1, 1 );
681 
682  if ( mLabelingEngine )
683  {
684  // set correct extent
687 
690  }
691 
692  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
693 
694  mDrawing = false;
695 }
696 
698 {
700 
701  // Since the map units have changed, force a recalculation of the scale.
702  updateScale();
703 
704  emit mapUnitsChanged();
705 }
706 
708 {
709  return mScaleCalculator->mapUnits();
710 }
711 
712 void QgsMapRenderer::onDrawingProgress( int current, int total )
713 {
714  Q_UNUSED( current );
715  Q_UNUSED( total );
716  // TODO: emit signal with progress
717 // QgsDebugMsg(QString("onDrawingProgress: %1 / %2").arg(current).arg(total));
718  emit updateMap();
719 }
720 
722 {
723  if ( mProjectionsEnabled != enabled )
724  {
725  mProjectionsEnabled = enabled;
726  QgsDebugMsg( "Adjusting DistArea projection on/off" );
727  mDistArea->setEllipsoidalMode( enabled );
730  emit hasCrsTransformEnabled( enabled );
731  }
732 }
733 
735 {
736  return mProjectionsEnabled;
737 }
738 
740 {
741  QgsDebugMsg( "* Setting destCRS : = " + crs.toProj4() );
742  QgsDebugMsg( "* DestCRS.srsid() = " + QString::number( crs.srsid() ) );
743  if ( *mDestCRS != crs )
744  {
745  QgsRectangle rect;
746  if ( !mExtent.isEmpty() )
747  {
748  QgsCoordinateTransform transform( *mDestCRS, crs );
749  rect = transform.transformBoundingBox( mExtent );
750  }
751 
752  QgsDebugMsg( "Setting DistArea CRS to " + QString::number( crs.srsid() ) );
753  mDistArea->setSourceCrs( crs.srsid() );
754  *mDestCRS = crs;
756 
757  if ( !rect.isEmpty() )
758  {
759  setExtent( rect );
760  }
761 
762  emit destinationSrsChanged();
763  }
764 }
765 
767 {
768  QgsDebugMsgLevel( "* Returning destCRS", 3 );
769  QgsDebugMsgLevel( "* DestCRS.srsid() = " + QString::number( mDestCRS->srsid() ), 3 );
770  QgsDebugMsgLevel( "* DestCRS.proj4() = " + mDestCRS->toProj4(), 3 );
771  return *mDestCRS;
772 }
773 
774 
776 {
777  bool split = false;
778 
779  if ( hasCrsTransformEnabled() )
780  {
781  try
782  {
783 #ifdef QGISDEBUG
784  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
785 #endif
786  // Split the extent into two if the source CRS is
787  // geographic and the extent crosses the split in
788  // geographic coordinates (usually +/- 180 degrees,
789  // and is assumed to be so here), and draw each
790  // extent separately.
791  static const double splitCoord = 180.0;
792 
793  if ( layer->crs().geographicFlag() )
794  {
795  // Note: ll = lower left point
796  // and ur = upper right point
797  QgsPoint ll = tr( layer )->transform( extent.xMinimum(), extent.yMinimum(),
799 
800  QgsPoint ur = tr( layer )->transform( extent.xMaximum(), extent.yMaximum(),
802 
804 
805  if ( ll.x() > ur.x() )
806  {
807  r2 = extent;
808  extent.setXMinimum( splitCoord );
809  r2.setXMaximum( splitCoord );
810  split = true;
811  }
812  }
813  else // can't cross 180
814  {
816  }
817  }
818  catch ( QgsCsException &cse )
819  {
820  Q_UNUSED( cse );
821  QgsDebugMsg( "Transform error caught" );
822  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
823  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
824  }
825  }
826  return split;
827 }
828 
830 {
831  QgsDebugMsg( QString( "sourceCrs = " + tr( theLayer )->sourceCrs().authid() ) );
832  QgsDebugMsg( QString( "destCRS = " + tr( theLayer )->destCRS().authid() ) );
833  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
834  if ( hasCrsTransformEnabled() )
835  {
836  try
837  {
838  extent = tr( theLayer )->transformBoundingBox( extent );
839  }
840  catch ( QgsCsException &cse )
841  {
842  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
843  }
844  }
845 
846  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
847 
848  return extent;
849 }
850 
852 {
853  QgsDebugMsg( QString( "layer sourceCrs = " + tr( theLayer )->sourceCrs().authid() ) );
854  QgsDebugMsg( QString( "layer destCRS = " + tr( theLayer )->destCRS().authid() ) );
855  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
856  if ( hasCrsTransformEnabled() )
857  {
858  try
859  {
860  extent = tr( theLayer )->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
861  }
862  catch ( QgsCsException &cse )
863  {
864  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
865  }
866  }
867 
868  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
869 
870  return extent;
871 }
872 
874 {
875  if ( hasCrsTransformEnabled() )
876  {
877  try
878  {
879  point = tr( theLayer )->transform( point, QgsCoordinateTransform::ForwardTransform );
880  }
881  catch ( QgsCsException &cse )
882  {
883  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
884  }
885  }
886  else
887  {
888  // leave point without transformation
889  }
890  return point;
891 }
892 
894 {
895  if ( hasCrsTransformEnabled() )
896  {
897  try
898  {
899  rect = tr( theLayer )->transform( rect, QgsCoordinateTransform::ForwardTransform );
900  }
901  catch ( QgsCsException &cse )
902  {
903  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
904  }
905  }
906  else
907  {
908  // leave point without transformation
909  }
910  return rect;
911 }
912 
914 {
915  if ( hasCrsTransformEnabled() )
916  {
917  try
918  {
919  point = tr( theLayer )->transform( point, QgsCoordinateTransform::ReverseTransform );
920  }
921  catch ( QgsCsException &cse )
922  {
923  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
924  }
925  }
926  else
927  {
928  // leave point without transformation
929  }
930  return point;
931 }
932 
934 {
935  if ( hasCrsTransformEnabled() )
936  {
937  try
938  {
939  rect = tr( theLayer )->transform( rect, QgsCoordinateTransform::ReverseTransform );
940  }
941  catch ( QgsCsException &cse )
942  {
943  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
944  }
945  }
946  return rect;
947 }
948 
949 
951 {
952  QgsDebugMsg( "called." );
954 
955  // reset the map canvas extent since the extent may now be smaller
956  // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction
958 
959  // iterate through the map layers and test each layers extent
960  // against the current min and max values
961  QStringList::iterator it = mLayerSet.begin();
962  QgsDebugMsg( QString( "Layer count: %1" ).arg( mLayerSet.count() ) );
963  while ( it != mLayerSet.end() )
964  {
965  QgsMapLayer * lyr = registry->mapLayer( *it );
966  if ( lyr == NULL )
967  {
968  QgsDebugMsg( QString( "WARNING: layer '%1' not found in map layer registry!" ).arg( *it ) );
969  }
970  else
971  {
972  QgsDebugMsg( "Updating extent using " + lyr->name() );
973  QgsDebugMsg( "Input extent: " + lyr->extent().toString() );
974 
975  if ( lyr->extent().isEmpty() )
976  {
977  it++;
978  continue;
979  }
980 
981  // Layer extents are stored in the coordinate system (CS) of the
982  // layer. The extent must be projected to the canvas CS
984 
985  QgsDebugMsg( "Output extent: " + extent.toString() );
986  mFullExtent.unionRect( extent );
987 
988  }
989  it++;
990  }
991 
992  if ( mFullExtent.width() == 0.0 || mFullExtent.height() == 0.0 )
993  {
994  // If all of the features are at the one point, buffer the
995  // rectangle a bit. If they are all at zero, do something a bit
996  // more crude.
997 
998  if ( mFullExtent.xMinimum() == 0.0 && mFullExtent.xMaximum() == 0.0 &&
999  mFullExtent.yMinimum() == 0.0 && mFullExtent.yMaximum() == 0.0 )
1000  {
1001  mFullExtent.set( -1.0, -1.0, 1.0, 1.0 );
1002  }
1003  else
1004  {
1005  const double padFactor = 1e-8;
1006  double widthPad = mFullExtent.xMinimum() * padFactor;
1007  double heightPad = mFullExtent.yMinimum() * padFactor;
1008  double xmin = mFullExtent.xMinimum() - widthPad;
1009  double xmax = mFullExtent.xMaximum() + widthPad;
1010  double ymin = mFullExtent.yMinimum() - heightPad;
1011  double ymax = mFullExtent.yMaximum() + heightPad;
1012  mFullExtent.set( xmin, ymin, xmax, ymax );
1013  }
1014  }
1015 
1016  QgsDebugMsg( "Full extent: " + mFullExtent.toString() );
1017 }
1018 
1020 {
1021  updateFullExtent();
1022  return mFullExtent;
1023 }
1024 
1025 void QgsMapRenderer::setLayerSet( const QStringList& layers )
1026 {
1027  QgsDebugMsg( QString( "Entering: %1" ).arg( layers.join( ", " ) ) );
1028  mLayerSet = layers;
1029  updateFullExtent();
1030 }
1031 
1033 {
1034  return mLayerSet;
1035 }
1036 
1037 bool QgsMapRenderer::readXML( QDomNode & theNode )
1038 {
1039  QDomNode myNode = theNode.namedItem( "units" );
1040  QDomElement element = myNode.toElement();
1041 
1042  // set units
1043  QGis::UnitType units;
1044  if ( "meters" == element.text() )
1045  {
1046  units = QGis::Meters;
1047  }
1048  else if ( "feet" == element.text() )
1049  {
1050  units = QGis::Feet;
1051  }
1052  else if ( "degrees" == element.text() )
1053  {
1054  units = QGis::Degrees;
1055  }
1056  else if ( "unknown" == element.text() )
1057  {
1058  units = QGis::UnknownUnit;
1059  }
1060  else
1061  {
1062  QgsDebugMsg( "Unknown map unit type " + element.text() );
1063  units = QGis::Degrees;
1064  }
1065  setMapUnits( units );
1066 
1067  // set projections flag
1068  QDomNode projNode = theNode.namedItem( "projections" );
1069  element = projNode.toElement();
1070  setProjectionsEnabled( element.text().toInt() );
1071 
1072  // set destination CRS
1074  QDomNode srsNode = theNode.namedItem( "destinationsrs" );
1075  srs.readXML( srsNode );
1076  setDestinationCrs( srs );
1077 
1078  // set extent
1079  QgsRectangle aoi;
1080  QDomNode extentNode = theNode.namedItem( "extent" );
1081 
1082  QDomNode xminNode = extentNode.namedItem( "xmin" );
1083  QDomNode yminNode = extentNode.namedItem( "ymin" );
1084  QDomNode xmaxNode = extentNode.namedItem( "xmax" );
1085  QDomNode ymaxNode = extentNode.namedItem( "ymax" );
1086 
1087  QDomElement exElement = xminNode.toElement();
1088  double xmin = exElement.text().toDouble();
1089  aoi.setXMinimum( xmin );
1090 
1091  exElement = yminNode.toElement();
1092  double ymin = exElement.text().toDouble();
1093  aoi.setYMinimum( ymin );
1094 
1095  exElement = xmaxNode.toElement();
1096  double xmax = exElement.text().toDouble();
1097  aoi.setXMaximum( xmax );
1098 
1099  exElement = ymaxNode.toElement();
1100  double ymax = exElement.text().toDouble();
1101  aoi.setYMaximum( ymax );
1102 
1103  setExtent( aoi );
1104  return true;
1105 }
1106 
1107 bool QgsMapRenderer::writeXML( QDomNode & theNode, QDomDocument & theDoc )
1108 {
1109  // units
1110 
1111  QDomElement unitsNode = theDoc.createElement( "units" );
1112  theNode.appendChild( unitsNode );
1113 
1114  QString unitsString;
1115 
1116  switch ( mapUnits() )
1117  {
1118  case QGis::Meters:
1119  unitsString = "meters";
1120  break;
1121  case QGis::Feet:
1122  unitsString = "feet";
1123  break;
1124  case QGis::Degrees:
1125  unitsString = "degrees";
1126  break;
1127  case QGis::UnknownUnit:
1128  default:
1129  unitsString = "unknown";
1130  break;
1131  }
1132  QDomText unitsText = theDoc.createTextNode( unitsString );
1133  unitsNode.appendChild( unitsText );
1134 
1135 
1136  // Write current view extents
1137  QDomElement extentNode = theDoc.createElement( "extent" );
1138  theNode.appendChild( extentNode );
1139 
1140  QDomElement xMin = theDoc.createElement( "xmin" );
1141  QDomElement yMin = theDoc.createElement( "ymin" );
1142  QDomElement xMax = theDoc.createElement( "xmax" );
1143  QDomElement yMax = theDoc.createElement( "ymax" );
1144 
1145  QgsRectangle r = extent();
1146  QDomText xMinText = theDoc.createTextNode( qgsDoubleToString( r.xMinimum() ) );
1147  QDomText yMinText = theDoc.createTextNode( qgsDoubleToString( r.yMinimum() ) );
1148  QDomText xMaxText = theDoc.createTextNode( qgsDoubleToString( r.xMaximum() ) );
1149  QDomText yMaxText = theDoc.createTextNode( qgsDoubleToString( r.yMaximum() ) );
1150 
1151  xMin.appendChild( xMinText );
1152  yMin.appendChild( yMinText );
1153  xMax.appendChild( xMaxText );
1154  yMax.appendChild( yMaxText );
1155 
1156  extentNode.appendChild( xMin );
1157  extentNode.appendChild( yMin );
1158  extentNode.appendChild( xMax );
1159  extentNode.appendChild( yMax );
1160 
1161  // projections enabled
1162  QDomElement projNode = theDoc.createElement( "projections" );
1163  theNode.appendChild( projNode );
1164 
1165  QDomText projText = theDoc.createTextNode( QString::number( hasCrsTransformEnabled() ) );
1166  projNode.appendChild( projText );
1167 
1168  // destination CRS
1169  QDomElement srsNode = theDoc.createElement( "destinationsrs" );
1170  theNode.appendChild( srsNode );
1171  destinationCrs().writeXML( srsNode, theDoc );
1172 
1173  return true;
1174 }
1175 
1177 {
1178  if ( mLabelingEngine )
1179  delete mLabelingEngine;
1180 
1181  mLabelingEngine = iface;
1182 }
1183 
1185 {
1186  if ( !layer || !mDestCRS )
1187  {
1188  return 0;
1189  }
1191 }
1192 
1195 QPainter::CompositionMode QgsMapRenderer::getCompositionMode( const QgsMapRenderer::BlendMode blendMode )
1196 {
1197  // Map QgsMapRenderer::BlendNormal to QPainter::CompositionMode
1198  switch ( blendMode )
1199  {
1201  return QPainter::CompositionMode_SourceOver;
1203  return QPainter::CompositionMode_Lighten;
1205  return QPainter::CompositionMode_Screen;
1207  return QPainter::CompositionMode_ColorDodge;
1209  return QPainter::CompositionMode_Plus;
1211  return QPainter::CompositionMode_Darken;
1213  return QPainter::CompositionMode_Multiply;
1215  return QPainter::CompositionMode_ColorBurn;
1217  return QPainter::CompositionMode_Overlay;
1219  return QPainter::CompositionMode_SoftLight;
1221  return QPainter::CompositionMode_HardLight;
1223  return QPainter::CompositionMode_Difference;
1225  return QPainter::CompositionMode_Exclusion;
1226  default:
1227  return QPainter::CompositionMode_SourceOver;
1228  }
1229 }
1230 
1231 QgsMapRenderer::BlendMode QgsMapRenderer::getBlendModeEnum( const QPainter::CompositionMode blendMode )
1232 {
1233  // Map QPainter::CompositionMode to QgsMapRenderer::BlendNormal
1234  switch ( blendMode )
1235  {
1236  case QPainter::CompositionMode_SourceOver:
1238  case QPainter::CompositionMode_Lighten:
1240  case QPainter::CompositionMode_Screen:
1242  case QPainter::CompositionMode_ColorDodge:
1244  case QPainter::CompositionMode_Plus:
1246  case QPainter::CompositionMode_Darken:
1248  case QPainter::CompositionMode_Multiply:
1250  case QPainter::CompositionMode_ColorBurn:
1252  case QPainter::CompositionMode_Overlay:
1254  case QPainter::CompositionMode_SoftLight:
1256  case QPainter::CompositionMode_HardLight:
1258  case QPainter::CompositionMode_Difference:
1260  case QPainter::CompositionMode_Exclusion:
1262  default:
1264  }
1265 }
1266 
1267 bool QgsMapRenderer::mDrawing = false;