QGIS API Documentation  2.2.0-Valmiera
 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 );
415  ct = transformation( ml );
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 = transformation( ml );
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 ); // deprecated
731  emit hasCrsTransformEnabledChanged( enabled );
732  }
733 }
734 
736 {
737  return mProjectionsEnabled;
738 }
739 
740 void QgsMapRenderer::setDestinationCrs( const QgsCoordinateReferenceSystem& crs, bool refreshCoordinateTransformInfo )
741 {
742  QgsDebugMsg( "* Setting destCRS : = " + crs.toProj4() );
743  QgsDebugMsg( "* DestCRS.srsid() = " + QString::number( crs.srsid() ) );
744  if ( *mDestCRS != crs )
745  {
746  if ( refreshCoordinateTransformInfo )
747  {
749  }
750  QgsRectangle rect;
751  if ( !mExtent.isEmpty() )
752  {
753  QgsCoordinateTransform transform( *mDestCRS, crs );
754  rect = transform.transformBoundingBox( mExtent );
755  }
756 
757  QgsDebugMsg( "Setting DistArea CRS to " + QString::number( crs.srsid() ) );
758  mDistArea->setSourceCrs( crs.srsid() );
759  *mDestCRS = crs;
761 
762  if ( !rect.isEmpty() )
763  {
764  setExtent( rect );
765  }
766 
767  emit destinationSrsChanged();
768  }
769 }
770 
772 {
773  QgsDebugMsgLevel( "* Returning destCRS", 3 );
774  QgsDebugMsgLevel( "* DestCRS.srsid() = " + QString::number( mDestCRS->srsid() ), 3 );
775  QgsDebugMsgLevel( "* DestCRS.proj4() = " + mDestCRS->toProj4(), 3 );
776  return *mDestCRS;
777 }
778 
779 
781 {
782  bool split = false;
783 
784  if ( hasCrsTransformEnabled() )
785  {
786  try
787  {
788 #ifdef QGISDEBUG
789  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
790 #endif
791  // Split the extent into two if the source CRS is
792  // geographic and the extent crosses the split in
793  // geographic coordinates (usually +/- 180 degrees,
794  // and is assumed to be so here), and draw each
795  // extent separately.
796  static const double splitCoord = 180.0;
797 
798  const QgsCoordinateTransform *transform = transformation( layer );
799  if ( layer->crs().geographicFlag() )
800  {
801  // Note: ll = lower left point
802  // and ur = upper right point
803 
804  QgsPoint ll( extent.xMinimum(), extent.yMinimum() );
805  QgsPoint ur( extent.xMaximum(), extent.yMaximum() );
806 
807  if ( transform )
808  {
809  ll = transform->transform( ll.x(), ll.y(),
811  ur = transform->transform( ur.x(), ur.y(),
813  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
814  }
815 
816  if ( ll.x() > ur.x() )
817  {
818  r2 = extent;
819  extent.setXMinimum( splitCoord );
820  r2.setXMaximum( splitCoord );
821  split = true;
822  }
823  }
824  else // can't cross 180
825  {
826  if ( transform )
827  {
828  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
829  }
830  }
831  }
832  catch ( QgsCsException &cse )
833  {
834  Q_UNUSED( cse );
835  QgsDebugMsg( "Transform error caught" );
836  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
837  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
838  }
839  }
840  return split;
841 }
842 
844 {
845  //QgsDebugMsg( QString( "sourceCrs = " + tr( theLayer )->sourceCrs().authid() ) );
846  //QgsDebugMsg( QString( "destCRS = " + tr( theLayer )->destCRS().authid() ) );
847  //QgsDebugMsg( QString( "extent = " + extent.toString() ) );
848  if ( hasCrsTransformEnabled() )
849  {
850  try
851  {
852  const QgsCoordinateTransform *transform = transformation( theLayer );
853  if ( transform )
854  {
855  extent = transform->transformBoundingBox( extent );
856  }
857  }
858  catch ( QgsCsException &cse )
859  {
860  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
861  }
862  }
863 
864  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
865 
866  return extent;
867 }
868 
870 {
871 #if QGISDEBUG
872  const QgsCoordinateTransform *transform = transformation( theLayer );
873  QgsDebugMsg( QString( "layer sourceCrs = " + ( transform ? transform->sourceCrs().authid() : "none" ) ) );
874  QgsDebugMsg( QString( "layer destCRS = " + ( transform ? transform->destCRS().authid() : "none" ) ) );
875  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
876 #endif
877  if ( hasCrsTransformEnabled() )
878  {
879  try
880  {
881  const QgsCoordinateTransform *transform = transformation( theLayer );
882  if ( transform )
883  {
884  extent = transform->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
885  }
886  }
887  catch ( QgsCsException &cse )
888  {
889  QgsMessageLog::logMessage( tr( "Transform error caught: %1" ).arg( cse.what() ), tr( "CRS" ) );
890  }
891  }
892 
893  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
894 
895  return extent;
896 }
897 
899 {
900  if ( hasCrsTransformEnabled() )
901  {
902  try
903  {
904  const QgsCoordinateTransform *transform = transformation( theLayer );
905  if ( transform )
906  {
907  point = transform->transform( point, QgsCoordinateTransform::ForwardTransform );
908  }
909  }
910  catch ( QgsCsException &cse )
911  {
912  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
913  }
914  }
915  else
916  {
917  // leave point without transformation
918  }
919  return point;
920 }
921 
923 {
924  if ( hasCrsTransformEnabled() )
925  {
926  try
927  {
928  const QgsCoordinateTransform *transform = transformation( theLayer );
929  if ( transform )
930  {
931  rect = transform->transform( rect, QgsCoordinateTransform::ForwardTransform );
932  }
933  }
934  catch ( QgsCsException &cse )
935  {
936  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
937  }
938  }
939  else
940  {
941  // leave point without transformation
942  }
943  return rect;
944 }
945 
947 {
948  if ( hasCrsTransformEnabled() )
949  {
950  try
951  {
952  const QgsCoordinateTransform *transform = transformation( theLayer );
953  if ( transform )
954  point = transform->transform( point, QgsCoordinateTransform::ReverseTransform );
955  }
956  catch ( QgsCsException &cse )
957  {
958  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
959  }
960  }
961  else
962  {
963  // leave point without transformation
964  }
965  return point;
966 }
967 
969 {
970  if ( hasCrsTransformEnabled() )
971  {
972  try
973  {
974  const QgsCoordinateTransform *transform = transformation( theLayer );
975  if ( transform )
976  rect = transform->transform( rect, QgsCoordinateTransform::ReverseTransform );
977  }
978  catch ( QgsCsException &cse )
979  {
980  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ) );
981  }
982  }
983  return rect;
984 }
985 
986 
988 {
989  QgsDebugMsg( "called." );
991 
992  // reset the map canvas extent since the extent may now be smaller
993  // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction
995 
996  // iterate through the map layers and test each layers extent
997  // against the current min and max values
998  QStringList::iterator it = mLayerSet.begin();
999  QgsDebugMsg( QString( "Layer count: %1" ).arg( mLayerSet.count() ) );
1000  while ( it != mLayerSet.end() )
1001  {
1002  QgsMapLayer * lyr = registry->mapLayer( *it );
1003  if ( lyr == NULL )
1004  {
1005  QgsDebugMsg( QString( "WARNING: layer '%1' not found in map layer registry!" ).arg( *it ) );
1006  }
1007  else
1008  {
1009  QgsDebugMsg( "Updating extent using " + lyr->name() );
1010  QgsDebugMsg( "Input extent: " + lyr->extent().toString() );
1011 
1012  if ( lyr->extent().isEmpty() )
1013  {
1014  ++it;
1015  continue;
1016  }
1017 
1018  // Layer extents are stored in the coordinate system (CS) of the
1019  // layer. The extent must be projected to the canvas CS
1021 
1022  QgsDebugMsg( "Output extent: " + extent.toString() );
1023  mFullExtent.unionRect( extent );
1024 
1025  }
1026  ++it;
1027  }
1028 
1029  if ( mFullExtent.width() == 0.0 || mFullExtent.height() == 0.0 )
1030  {
1031  // If all of the features are at the one point, buffer the
1032  // rectangle a bit. If they are all at zero, do something a bit
1033  // more crude.
1034 
1035  if ( mFullExtent.xMinimum() == 0.0 && mFullExtent.xMaximum() == 0.0 &&
1036  mFullExtent.yMinimum() == 0.0 && mFullExtent.yMaximum() == 0.0 )
1037  {
1038  mFullExtent.set( -1.0, -1.0, 1.0, 1.0 );
1039  }
1040  else
1041  {
1042  const double padFactor = 1e-8;
1043  double widthPad = mFullExtent.xMinimum() * padFactor;
1044  double heightPad = mFullExtent.yMinimum() * padFactor;
1045  double xmin = mFullExtent.xMinimum() - widthPad;
1046  double xmax = mFullExtent.xMaximum() + widthPad;
1047  double ymin = mFullExtent.yMinimum() - heightPad;
1048  double ymax = mFullExtent.yMaximum() + heightPad;
1049  mFullExtent.set( xmin, ymin, xmax, ymax );
1050  }
1051  }
1052 
1053  QgsDebugMsg( "Full extent: " + mFullExtent.toString() );
1054 }
1055 
1057 {
1058  updateFullExtent();
1059  return mFullExtent;
1060 }
1061 
1062 void QgsMapRenderer::setLayerSet( const QStringList& layers )
1063 {
1064  QgsDebugMsg( QString( "Entering: %1" ).arg( layers.join( ", " ) ) );
1065  mLayerSet = layers;
1066  updateFullExtent();
1067 }
1068 
1070 {
1071  return mLayerSet;
1072 }
1073 
1074 bool QgsMapRenderer::readXML( QDomNode & theNode )
1075 {
1076  QDomNode myNode = theNode.namedItem( "units" );
1077  QDomElement element = myNode.toElement();
1078 
1079  // set units
1080  QGis::UnitType units;
1081  if ( "meters" == element.text() )
1082  {
1083  units = QGis::Meters;
1084  }
1085  else if ( "feet" == element.text() )
1086  {
1087  units = QGis::Feet;
1088  }
1089  else if ( "nautical miles" == element.text() )
1090  {
1091  units = QGis::NauticalMiles;
1092  }
1093  else if ( "degrees" == element.text() )
1094  {
1095  units = QGis::Degrees;
1096  }
1097  else if ( "unknown" == element.text() )
1098  {
1099  units = QGis::UnknownUnit;
1100  }
1101  else
1102  {
1103  QgsDebugMsg( "Unknown map unit type " + element.text() );
1104  units = QGis::Degrees;
1105  }
1106  setMapUnits( units );
1107 
1108  // set projections flag
1109  QDomNode projNode = theNode.namedItem( "projections" );
1110  element = projNode.toElement();
1111  setProjectionsEnabled( element.text().toInt() );
1112 
1113  //load coordinate transform into
1115  QDomElement layerCoordTransformInfoElem = theNode.firstChildElement( "layer_coordinate_transform_info" );
1116  if ( !layerCoordTransformInfoElem.isNull() )
1117  {
1118  QDomNodeList layerCoordinateTransformList = layerCoordTransformInfoElem.elementsByTagName( "layer_coordinate_transform" );
1119  QDomElement layerCoordTransformElem;
1120  for ( int i = 0; i < layerCoordinateTransformList.size(); ++i )
1121  {
1122  layerCoordTransformElem = layerCoordinateTransformList.at( i ).toElement();
1123  QString layerId = layerCoordTransformElem.attribute( "layerid" );
1124  if ( layerId.isEmpty() )
1125  {
1126  continue;
1127  }
1128 
1130  lct.srcAuthId = layerCoordTransformElem.attribute( "srcAuthId" );
1131  lct.destAuthId = layerCoordTransformElem.attribute( "destAuthId" );
1132  lct.srcDatumTransform = layerCoordTransformElem.attribute( "srcDatumTransform", "-1" ).toInt();
1133  lct.destDatumTransform = layerCoordTransformElem.attribute( "destDatumTransform", "-1" ).toInt();
1134  mLayerCoordinateTransformInfo.insert( layerId, lct );
1135  }
1136  }
1137 
1138  // set destination CRS
1140  QDomNode srsNode = theNode.namedItem( "destinationsrs" );
1141  srs.readXML( srsNode );
1142  setDestinationCrs( srs, false );
1143 
1144  // set extent
1145  QgsRectangle aoi;
1146  QDomNode extentNode = theNode.namedItem( "extent" );
1147 
1148  QDomNode xminNode = extentNode.namedItem( "xmin" );
1149  QDomNode yminNode = extentNode.namedItem( "ymin" );
1150  QDomNode xmaxNode = extentNode.namedItem( "xmax" );
1151  QDomNode ymaxNode = extentNode.namedItem( "ymax" );
1152 
1153  QDomElement exElement = xminNode.toElement();
1154  double xmin = exElement.text().toDouble();
1155  aoi.setXMinimum( xmin );
1156 
1157  exElement = yminNode.toElement();
1158  double ymin = exElement.text().toDouble();
1159  aoi.setYMinimum( ymin );
1160 
1161  exElement = xmaxNode.toElement();
1162  double xmax = exElement.text().toDouble();
1163  aoi.setXMaximum( xmax );
1164 
1165  exElement = ymaxNode.toElement();
1166  double ymax = exElement.text().toDouble();
1167  aoi.setYMaximum( ymax );
1168 
1169  setExtent( aoi );
1170 
1171  return true;
1172 }
1173 
1174 bool QgsMapRenderer::writeXML( QDomNode & theNode, QDomDocument & theDoc )
1175 {
1176  // units
1177 
1178  QDomElement unitsNode = theDoc.createElement( "units" );
1179  theNode.appendChild( unitsNode );
1180 
1181  QString unitsString;
1182 
1183  switch ( mapUnits() )
1184  {
1185  case QGis::Meters:
1186  unitsString = "meters";
1187  break;
1188  case QGis::Feet:
1189  unitsString = "feet";
1190  break;
1191  case QGis::NauticalMiles:
1192  unitsString = "nautical miles";
1193  break;
1194  case QGis::Degrees:
1195  unitsString = "degrees";
1196  break;
1197  case QGis::UnknownUnit:
1198  default:
1199  unitsString = "unknown";
1200  break;
1201  }
1202  QDomText unitsText = theDoc.createTextNode( unitsString );
1203  unitsNode.appendChild( unitsText );
1204 
1205 
1206  // Write current view extents
1207  QDomElement extentNode = theDoc.createElement( "extent" );
1208  theNode.appendChild( extentNode );
1209 
1210  QDomElement xMin = theDoc.createElement( "xmin" );
1211  QDomElement yMin = theDoc.createElement( "ymin" );
1212  QDomElement xMax = theDoc.createElement( "xmax" );
1213  QDomElement yMax = theDoc.createElement( "ymax" );
1214 
1215  QgsRectangle r = extent();
1216  QDomText xMinText = theDoc.createTextNode( qgsDoubleToString( r.xMinimum() ) );
1217  QDomText yMinText = theDoc.createTextNode( qgsDoubleToString( r.yMinimum() ) );
1218  QDomText xMaxText = theDoc.createTextNode( qgsDoubleToString( r.xMaximum() ) );
1219  QDomText yMaxText = theDoc.createTextNode( qgsDoubleToString( r.yMaximum() ) );
1220 
1221  xMin.appendChild( xMinText );
1222  yMin.appendChild( yMinText );
1223  xMax.appendChild( xMaxText );
1224  yMax.appendChild( yMaxText );
1225 
1226  extentNode.appendChild( xMin );
1227  extentNode.appendChild( yMin );
1228  extentNode.appendChild( xMax );
1229  extentNode.appendChild( yMax );
1230 
1231  // projections enabled
1232  QDomElement projNode = theDoc.createElement( "projections" );
1233  theNode.appendChild( projNode );
1234 
1235  QDomText projText = theDoc.createTextNode( QString::number( hasCrsTransformEnabled() ) );
1236  projNode.appendChild( projText );
1237 
1238  // destination CRS
1239  QDomElement srsNode = theDoc.createElement( "destinationsrs" );
1240  theNode.appendChild( srsNode );
1241  destinationCrs().writeXML( srsNode, theDoc );
1242 
1243  // layer coordinate transform infos
1244  QDomElement layerCoordTransformInfo = theDoc.createElement( "layer_coordinate_transform_info" );
1245  QHash< QString, QgsLayerCoordinateTransform >::const_iterator coordIt = mLayerCoordinateTransformInfo.constBegin();
1246  for ( ; coordIt != mLayerCoordinateTransformInfo.constEnd(); ++coordIt )
1247  {
1248  QDomElement layerCoordTransformElem = theDoc.createElement( "layer_coordinate_transform" );
1249  layerCoordTransformElem.setAttribute( "layerid", coordIt.key() );
1250  layerCoordTransformElem.setAttribute( "srcAuthId", coordIt->srcAuthId );
1251  layerCoordTransformElem.setAttribute( "destAuthId", coordIt->destAuthId );
1252  layerCoordTransformElem.setAttribute( "srcDatumTransform", QString::number( coordIt->srcDatumTransform ) );
1253  layerCoordTransformElem.setAttribute( "destDatumTransform", QString::number( coordIt->destDatumTransform ) );
1254  layerCoordTransformInfo.appendChild( layerCoordTransformElem );
1255  }
1256  theNode.appendChild( layerCoordTransformInfo );
1257  return true;
1258 }
1259 
1261 {
1262  if ( mLabelingEngine )
1263  delete mLabelingEngine;
1264 
1265  mLabelingEngine = iface;
1266 }
1267 
1269 {
1270  if ( !layer || !mDestCRS )
1271  {
1272  return 0;
1273  }
1274 
1275  if ( layer->crs().authid() == mDestCRS->authid() )
1276  {
1277  return 0;
1278  }
1279 
1280  QHash< QString, QgsLayerCoordinateTransform >::const_iterator ctIt = mLayerCoordinateTransformInfo.find( layer->id() );
1281  if ( ctIt != mLayerCoordinateTransformInfo.constEnd()
1282  && ctIt->srcAuthId == layer->crs().authid()
1283  && ctIt->destAuthId == mDestCRS->authid() )
1284  {
1285  return QgsCoordinateTransformCache::instance()->transform( ctIt->srcAuthId, ctIt->destAuthId, ctIt->srcDatumTransform, ctIt->destDatumTransform );
1286  }
1287  else
1288  {
1289  emit datumTransformInfoRequested( layer, layer->crs().authid(), mDestCRS->authid() );
1290  }
1291 
1292  //still not present? get coordinate transformation with -1/-1 datum transform as default
1293  ctIt = mLayerCoordinateTransformInfo.find( layer->id() );
1294  if ( ctIt == mLayerCoordinateTransformInfo.constEnd()
1295  || ctIt->srcAuthId == layer->crs().authid()
1296  || ctIt->destAuthId == mDestCRS->authid()
1297  )
1298  {
1299  return QgsCoordinateTransformCache::instance()->transform( layer->crs().authid(), mDestCRS->authid(), -1, -1 );
1300  }
1301  return QgsCoordinateTransformCache::instance()->transform( ctIt->srcAuthId, ctIt->destAuthId, ctIt->srcDatumTransform, ctIt->destDatumTransform );
1302 }
1303 
1306 QPainter::CompositionMode QgsMapRenderer::getCompositionMode( const QgsMapRenderer::BlendMode &blendMode )
1307 {
1308  // Map QgsMapRenderer::BlendNormal to QPainter::CompositionMode
1309  switch ( blendMode )
1310  {
1312  return QPainter::CompositionMode_SourceOver;
1314  return QPainter::CompositionMode_Lighten;
1316  return QPainter::CompositionMode_Screen;
1318  return QPainter::CompositionMode_ColorDodge;
1320  return QPainter::CompositionMode_Plus;
1322  return QPainter::CompositionMode_Darken;
1324  return QPainter::CompositionMode_Multiply;
1326  return QPainter::CompositionMode_ColorBurn;
1328  return QPainter::CompositionMode_Overlay;
1330  return QPainter::CompositionMode_SoftLight;
1332  return QPainter::CompositionMode_HardLight;
1334  return QPainter::CompositionMode_Difference;
1336  return QPainter::CompositionMode_Exclusion;
1337  default:
1338  return QPainter::CompositionMode_SourceOver;
1339  }
1340 }
1341 
1342 QgsMapRenderer::BlendMode QgsMapRenderer::getBlendModeEnum( const QPainter::CompositionMode &blendMode )
1343 {
1344  // Map QPainter::CompositionMode to QgsMapRenderer::BlendNormal
1345  switch ( blendMode )
1346  {
1347  case QPainter::CompositionMode_SourceOver:
1349  case QPainter::CompositionMode_Lighten:
1351  case QPainter::CompositionMode_Screen:
1353  case QPainter::CompositionMode_ColorDodge:
1355  case QPainter::CompositionMode_Plus:
1357  case QPainter::CompositionMode_Darken:
1359  case QPainter::CompositionMode_Multiply:
1361  case QPainter::CompositionMode_ColorBurn:
1363  case QPainter::CompositionMode_Overlay:
1365  case QPainter::CompositionMode_SoftLight:
1367  case QPainter::CompositionMode_HardLight:
1369  case QPainter::CompositionMode_Difference:
1371  case QPainter::CompositionMode_Exclusion:
1373  default:
1375  }
1376 }
1377 
1378 void QgsMapRenderer::addLayerCoordinateTransform( const QString& layerId, const QString& srcAuthId, const QString& destAuthId, int srcDatumTransform, int destDatumTransform )
1379 {
1381  lt.srcAuthId = srcAuthId;
1382  lt.destAuthId = destAuthId;
1383  lt.srcDatumTransform = srcDatumTransform;
1384  lt.destDatumTransform = destDatumTransform;
1385  mLayerCoordinateTransformInfo.insert( layerId, lt );
1386 }
1387 
1389 {
1391 }
1392 
1393 bool QgsMapRenderer::mDrawing = false;