QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsmapsettings.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmapsettings.cpp
3  --------------------------------------
4  Date : December 2013
5  Copyright : (C) 2013 by Martin Dobias
6  Email : wonder dot 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 "qgsmapsettings.h"
17 
18 #include "qgsscalecalculator.h"
19 #include "qgsmaprendererjob.h"
20 #include "qgsmaptopixel.h"
21 #include "qgslogger.h"
22 
23 #include "qgscrscache.h"
24 #include "qgsmessagelog.h"
25 #include "qgsmaplayer.h"
26 #include "qgsmaplayerregistry.h"
27 #include "qgsxmlutils.h"
28 
29 
30 Q_GUI_EXPORT extern int qt_defaultDpiX();
31 
32 
34  : mDpi( qt_defaultDpiX() ) // DPI that will be used by default for QImage instances
35  , mSize( QSize( 0, 0 ) )
36  , mExtent()
37  , mRotation( 0.0 )
38  , mProjectionsEnabled( false )
39  , mDestCRS( GEOCRS_ID, QgsCoordinateReferenceSystem::InternalCrsId ) // WGS 84
40  , mDatumTransformStore( mDestCRS )
41  , mBackgroundColor( Qt::white )
42  , mSelectionColor( Qt::yellow )
43  , mFlags( Antialiasing | UseAdvancedEffects | DrawLabeling | DrawSelection )
44  , mImageFormat( QImage::Format_ARGB32_Premultiplied )
45  , mValid( false )
46  , mVisibleExtent()
47  , mMapUnitsPerPixel( 1 )
48  , mScale( 1 )
49 {
50  updateDerived();
51 
52  // set default map units - we use WGS 84 thus use degrees
54 }
55 
56 
58 {
59  return mExtent;
60 }
61 
63 {
64  mExtent = extent;
65 
66  updateDerived();
67 }
68 
70 {
71  return mRotation;
72 }
73 
74 void QgsMapSettings::setRotation( double degrees )
75 {
76  if ( mRotation == degrees ) return;
77 
78  mRotation = degrees;
79 
80  // TODO: update extent while keeping scale ?
81  updateDerived();
82 }
83 
84 
86 {
88 
89  if ( extent.isEmpty() || !extent.isFinite() )
90  {
91  mValid = false;
92  return;
93  }
94 
95  // Don't allow zooms where the current extent is so small that it
96  // can't be accurately represented using a double (which is what
97  // currentExtent uses). Excluding 0 avoids a divide by zero and an
98  // infinite loop when rendering to a new canvas. Excluding extents
99  // greater than 1 avoids doing unnecessary calculations.
100 
101  // The scheme is to compare the width against the mean x coordinate
102  // (and height against mean y coordinate) and only allow zooms where
103  // the ratio indicates that there is more than about 12 significant
104  // figures (there are about 16 significant figures in a double).
105 
106  if ( extent.width() > 0 &&
107  extent.height() > 0 &&
108  extent.width() < 1 &&
109  extent.height() < 1 )
110  {
111  // Use abs() on the extent to avoid the case where the extent is
112  // symmetrical about 0.
113  double xMean = ( qAbs( extent.xMinimum() ) + qAbs( extent.xMaximum() ) ) * 0.5;
114  double yMean = ( qAbs( extent.yMinimum() ) + qAbs( extent.yMaximum() ) ) * 0.5;
115 
116  double xRange = extent.width() / xMean;
117  double yRange = extent.height() / yMean;
118 
119  static const double minProportion = 1e-12;
120  if ( xRange < minProportion || yRange < minProportion )
121  {
122  mValid = false;
123  return;
124  }
125  }
126 
127  double myHeight = mSize.height();
128  double myWidth = mSize.width();
129 
130  if ( !myWidth || !myHeight )
131  {
132  mValid = false;
133  return;
134  }
135 
136  // calculate the translation and scaling parameters
137  double mapUnitsPerPixelY = mExtent.height() / myHeight;
138  double mapUnitsPerPixelX = mExtent.width() / myWidth;
139  mMapUnitsPerPixel = mapUnitsPerPixelY > mapUnitsPerPixelX ? mapUnitsPerPixelY : mapUnitsPerPixelX;
140 
141  // calculate the actual extent of the mapCanvas
142  double dxmin = mExtent.xMinimum(), dxmax = mExtent.xMaximum(),
143  dymin = mExtent.yMinimum(), dymax = mExtent.yMaximum(), whitespace;
144 
145  if ( mapUnitsPerPixelY > mapUnitsPerPixelX )
146  {
147  whitespace = (( myWidth * mMapUnitsPerPixel ) - mExtent.width() ) * 0.5;
148  dxmin -= whitespace;
149  dxmax += whitespace;
150  }
151  else
152  {
153  whitespace = (( myHeight * mMapUnitsPerPixel ) - mExtent.height() ) * 0.5;
154  dymin -= whitespace;
155  dymax += whitespace;
156  }
157 
158  mVisibleExtent.set( dxmin, dymin, dxmax, dymax );
159 
160  // update the scale
163 
165  visibleExtent().center().x(),
166  visibleExtent().center().y(),
167  outputSize().width(),
168  outputSize().height(),
169  mRotation );
170 
171 #if 1 // set visible extent taking rotation in consideration
172  if ( mRotation )
173  {
174  QgsPoint p1 = mMapToPixel.toMapCoordinates( QPoint( 0, 0 ) );
175  QgsPoint p2 = mMapToPixel.toMapCoordinates( QPoint( 0, myHeight ) );
176  QgsPoint p3 = mMapToPixel.toMapCoordinates( QPoint( myWidth, 0 ) );
177  QgsPoint p4 = mMapToPixel.toMapCoordinates( QPoint( myWidth, myHeight ) );
178  dxmin = std::min( p1.x(), std::min( p2.x(), std::min( p3.x(), p4.x() ) ) );
179  dymin = std::min( p1.y(), std::min( p2.y(), std::min( p3.y(), p4.y() ) ) );
180  dxmax = std::max( p1.x(), std::max( p2.x(), std::max( p3.x(), p4.x() ) ) );
181  dymax = std::max( p1.y(), std::max( p2.y(), std::max( p3.y(), p4.y() ) ) );
182  mVisibleExtent.set( dxmin, dymin, dxmax, dymax );
183  }
184 #endif
185 
186  QgsDebugMsg( QString( "Map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mapUnitsPerPixelX ) ).arg( qgsDoubleToString( mapUnitsPerPixelY ) ) );
187  QgsDebugMsg( QString( "Pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mSize.width() ) ).arg( qgsDoubleToString( mSize.height() ) ) );
188  QgsDebugMsg( QString( "Extent dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mExtent.width() ) ).arg( qgsDoubleToString( mExtent.height() ) ) );
190  QgsDebugMsg( QString( "Adjusted map units per pixel (x,y) : %1, %2" ).arg( qgsDoubleToString( mVisibleExtent.width() / myWidth ) ).arg( qgsDoubleToString( mVisibleExtent.height() / myHeight ) ) );
191  QgsDebugMsg( QString( "Recalced pixmap dimensions (x,y) : %1, %2" ).arg( qgsDoubleToString( mVisibleExtent.width() / mMapUnitsPerPixel ) ).arg( qgsDoubleToString( mVisibleExtent.height() / mMapUnitsPerPixel ) ) );
192  QgsDebugMsg( QString( "Scale (assuming meters as map units) = 1:%1" ).arg( qgsDoubleToString( mScale ) ) );
193  QgsDebugMsg( QString( "Rotation: %1 degrees" ).arg( mRotation ) );
194 
195  mValid = true;
196 }
197 
198 
200 {
201  return mSize;
202 }
203 
205 {
206  mSize = size;
207 
208  updateDerived();
209 }
210 
212 {
213  return mDpi;
214 }
215 
217 {
218  mDpi = dpi;
219 
220  updateDerived();
221 }
222 
223 
224 QStringList QgsMapSettings::layers() const
225 {
226  return mLayers;
227 }
228 
229 void QgsMapSettings::setLayers( const QStringList& layers )
230 {
231  mLayers = layers;
232 }
233 
234 QMap<QString, QString> QgsMapSettings::layerStyleOverrides() const
235 {
236  return mLayerStyleOverrides;
237 }
238 
239 void QgsMapSettings::setLayerStyleOverrides( const QMap<QString, QString>& overrides )
240 {
241  mLayerStyleOverrides = overrides;
242 }
243 
245 {
246  mProjectionsEnabled = enabled;
247 }
248 
250 {
251  return mProjectionsEnabled;
252 }
253 
254 
256 {
257  mDestCRS = crs;
259 }
260 
262 {
263  return mDestCRS;
264 }
265 
266 
268 {
270 
271  // Since the map units have changed, force a recalculation of the scale.
272  updateDerived();
273 }
274 
275 void QgsMapSettings::setFlags( QgsMapSettings::Flags flags )
276 {
277  mFlags = flags;
278 }
279 
281 {
282  if ( on )
283  mFlags |= flag;
284  else
285  mFlags &= ~flag;
286 }
287 
288 QgsMapSettings::Flags QgsMapSettings::flags() const
289 {
290  return mFlags;
291 }
292 
294 {
295  return mFlags.testFlag( flag );
296 }
297 
299 {
300  return mScaleCalculator.mapUnits();
301 }
302 
303 
305 {
306  return mValid;
307 }
308 
310 {
311  return mVisibleExtent;
312 }
313 
315 {
316  QPolygonF poly;
317 
318  const QSize& sz = outputSize();
319  const QgsMapToPixel& m2p = mapToPixel();
320 
321  poly << m2p.toMapCoordinatesF( 0, 0 ).toQPointF();
322  poly << m2p.toMapCoordinatesF( sz.width(), 0 ).toQPointF();
323  poly << m2p.toMapCoordinatesF( sz.width(), sz.height() ).toQPointF();
324  poly << m2p.toMapCoordinatesF( 0, sz.height() ).toQPointF();
325 
326  return poly;
327 }
328 
330 {
331  return mMapUnitsPerPixel;
332 }
333 
334 double QgsMapSettings::scale() const
335 {
336  return mScale;
337 }
338 
339 
341 {
342  return mDatumTransformStore.transformation( layer );
343 }
344 
345 
347 {
348  if ( hasCrsTransformEnabled() )
349  {
350  try
351  {
352  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
353  {
354  QgsDebugMsg( QString( "sourceCrs = " + ct->sourceCrs().authid() ) );
355  QgsDebugMsg( QString( "destCRS = " + ct->destCRS().authid() ) );
356  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
357  extent = ct->transformBoundingBox( extent );
358  }
359  }
360  catch ( QgsCsException &cse )
361  {
362  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
363  }
364  }
365 
366  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
367 
368  return extent;
369 }
370 
371 
373 {
374  if ( hasCrsTransformEnabled() )
375  {
376  try
377  {
378  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
379  {
380  QgsDebugMsg( QString( "sourceCrs = " + ct->sourceCrs().authid() ) );
381  QgsDebugMsg( QString( "destCRS = " + ct->destCRS().authid() ) );
382  QgsDebugMsg( QString( "extent = " + extent.toString() ) );
383  extent = ct->transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
384  }
385  }
386  catch ( QgsCsException &cse )
387  {
388  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
389  }
390  }
391 
392  QgsDebugMsg( QString( "proj extent = " + extent.toString() ) );
393 
394  return extent;
395 }
396 
397 
399 {
400  if ( hasCrsTransformEnabled() )
401  {
402  try
403  {
404  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
405  point = ct->transform( point, QgsCoordinateTransform::ForwardTransform );
406  }
407  catch ( QgsCsException &cse )
408  {
409  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
410  }
411  }
412  else
413  {
414  // leave point without transformation
415  }
416  return point;
417 }
418 
419 
421 {
422  if ( hasCrsTransformEnabled() )
423  {
424  try
425  {
426  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
427  rect = ct->transform( rect, QgsCoordinateTransform::ForwardTransform );
428  }
429  catch ( QgsCsException &cse )
430  {
431  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
432  }
433  }
434  else
435  {
436  // leave point without transformation
437  }
438  return rect;
439 }
440 
441 
443 {
444  if ( hasCrsTransformEnabled() )
445  {
446  try
447  {
448  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
449  point = ct->transform( point, QgsCoordinateTransform::ReverseTransform );
450  }
451  catch ( QgsCsException &cse )
452  {
453  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
454  }
455  }
456  else
457  {
458  // leave point without transformation
459  }
460  return point;
461 }
462 
463 
465 {
466  if ( hasCrsTransformEnabled() )
467  {
468  try
469  {
470  if ( const QgsCoordinateTransform* ct = layerTransform( theLayer ) )
471  rect = ct->transform( rect, QgsCoordinateTransform::ReverseTransform );
472  }
473  catch ( QgsCsException &cse )
474  {
475  QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( cse.what() ), "CRS" );
476  }
477  }
478  return rect;
479 }
480 
481 
482 
484 {
485  QgsDebugMsg( "called." );
487 
488  // reset the map canvas extent since the extent may now be smaller
489  // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction
491  fullExtent.setMinimal();
492 
493  // iterate through the map layers and test each layers extent
494  // against the current min and max values
495  QStringList::const_iterator it = mLayers.begin();
496  QgsDebugMsg( QString( "Layer count: %1" ).arg( mLayers.count() ) );
497  while ( it != mLayers.end() )
498  {
499  QgsMapLayer * lyr = registry->mapLayer( *it );
500  if ( lyr == NULL )
501  {
502  QgsDebugMsg( QString( "WARNING: layer '%1' not found in map layer registry!" ).arg( *it ) );
503  }
504  else
505  {
506  QgsDebugMsg( "Updating extent using " + lyr->name() );
507  QgsDebugMsg( "Input extent: " + lyr->extent().toString() );
508 
509  if ( lyr->extent().isNull() )
510  {
511  ++it;
512  continue;
513  }
514 
515  // Layer extents are stored in the coordinate system (CS) of the
516  // layer. The extent must be projected to the canvas CS
518 
519  QgsDebugMsg( "Output extent: " + extent.toString() );
520  fullExtent.unionRect( extent );
521 
522  }
523  ++it;
524  }
525 
526  if ( fullExtent.width() == 0.0 || fullExtent.height() == 0.0 )
527  {
528  // If all of the features are at the one point, buffer the
529  // rectangle a bit. If they are all at zero, do something a bit
530  // more crude.
531 
532  if ( fullExtent.xMinimum() == 0.0 && fullExtent.xMaximum() == 0.0 &&
533  fullExtent.yMinimum() == 0.0 && fullExtent.yMaximum() == 0.0 )
534  {
535  fullExtent.set( -1.0, -1.0, 1.0, 1.0 );
536  }
537  else
538  {
539  const double padFactor = 1e-8;
540  double widthPad = fullExtent.xMinimum() * padFactor;
541  double heightPad = fullExtent.yMinimum() * padFactor;
542  double xmin = fullExtent.xMinimum() - widthPad;
543  double xmax = fullExtent.xMaximum() + widthPad;
544  double ymin = fullExtent.yMinimum() - heightPad;
545  double ymax = fullExtent.yMaximum() + heightPad;
546  fullExtent.set( xmin, ymin, xmax, ymax );
547  }
548  }
549 
550  QgsDebugMsg( "Full extent: " + fullExtent.toString() );
551  return fullExtent;
552 }
553 
554 
555 void QgsMapSettings::readXML( QDomNode& theNode )
556 {
557  // set units
558  QDomNode mapUnitsNode = theNode.namedItem( "units" );
559  QGis::UnitType units = QgsXmlUtils::readMapUnits( mapUnitsNode.toElement() );
560  setMapUnits( units );
561 
562  // set projections flag
563  QDomNode projNode = theNode.namedItem( "projections" );
564  setCrsTransformEnabled( projNode.toElement().text().toInt() );
565 
566  // set destination CRS
568  QDomNode srsNode = theNode.namedItem( "destinationsrs" );
569  srs.readXML( srsNode );
570  setDestinationCrs( srs );
571 
572  // set extent
573  QDomNode extentNode = theNode.namedItem( "extent" );
574  QgsRectangle aoi = QgsXmlUtils::readRectangle( extentNode.toElement() );
575  setExtent( aoi );
576 
577  // set rotation
578  QDomNode rotationNode = theNode.namedItem( "rotation" );
579  QString rotationVal = rotationNode.toElement().text();
580  if ( ! rotationVal.isEmpty() )
581  {
582  double rot = rotationVal.toDouble();
583  setRotation( rot );
584  }
585 
586  mDatumTransformStore.readXML( theNode );
587 }
588 
589 
590 
591 void QgsMapSettings::writeXML( QDomNode& theNode, QDomDocument& theDoc )
592 {
593  // units
594  theNode.appendChild( QgsXmlUtils::writeMapUnits( mapUnits(), theDoc ) );
595 
596  // Write current view extents
597  theNode.appendChild( QgsXmlUtils::writeRectangle( extent(), theDoc ) );
598 
599  // Write current view rotation
600  QDomElement rotNode = theDoc.createElement( "rotation" );
601  rotNode.appendChild(
602  theDoc.createTextNode( qgsDoubleToString( rotation() ) )
603  );
604  theNode.appendChild( rotNode );
605 
606  // projections enabled
607  QDomElement projNode = theDoc.createElement( "projections" );
608  projNode.appendChild( theDoc.createTextNode( QString::number( hasCrsTransformEnabled() ) ) );
609  theNode.appendChild( projNode );
610 
611  // destination CRS
612  QDomElement srsNode = theDoc.createElement( "destinationsrs" );
613  theNode.appendChild( srsNode );
614  destinationCrs().writeXML( srsNode, theDoc );
615 
616  mDatumTransformStore.writeXML( theNode, theDoc );
617 }