QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposermapoverview.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposermapoverview.cpp
3  --------------------
4  begin : July 2014
5  copyright : (C) 2014 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscomposermapoverview.h"
19 #include "qgscomposermap.h"
20 #include "qgscomposition.h"
21 #include "qgssymbollayerv2utils.h"
22 #include "qgssymbolv2.h"
23 
24 #include <QPainter>
25 
27  : QgsComposerMapItem( name, map )
28  , mFrameMapId( -1 )
29  , mFrameSymbol( 0 )
30  , mBlendMode( QPainter::CompositionMode_SourceOver )
31  , mInverted( false )
32  , mCentered( false )
33 {
34  createDefaultFrameSymbol();
35 }
36 
37 QgsComposerMapOverview::QgsComposerMapOverview()
38  : QgsComposerMapItem( QString(), 0 )
39  , mFrameMapId( -1 )
40  , mFrameSymbol( 0 )
41  , mBlendMode( QPainter::CompositionMode_SourceOver )
42  , mInverted( false )
43  , mCentered( false )
44 {
45 }
46 
47 void QgsComposerMapOverview::createDefaultFrameSymbol()
48 {
49  delete mFrameSymbol;
50  QgsStringMap properties;
51  properties.insert( "color", "255,0,0,255" );
52  properties.insert( "style", "solid" );
53  properties.insert( "style_border", "no" );
54  mFrameSymbol = QgsFillSymbolV2::createSimple( properties );
55  mFrameSymbol->setAlpha( 0.3 );
56 }
57 
59 {
60  delete mFrameSymbol;
61 }
62 
63 void QgsComposerMapOverview::draw( QPainter *painter )
64 {
65  if ( !mEnabled || mFrameMapId == -1 || !mComposerMap || !mComposerMap->composition() )
66  {
67  return;
68  }
69  if ( !painter )
70  {
71  return;
72  }
73 
74  const QgsComposerMap* overviewFrameMap = mComposerMap->composition()->getComposerMapById( mFrameMapId );
75  if ( !overviewFrameMap )
76  {
77  return;
78  }
79 
80  //get polygon for other overview frame map's extent (use visibleExtentPolygon as it accounts for map rotation)
81  QPolygonF otherExtent = overviewFrameMap->visibleExtentPolygon();
82 
83  //get current map's extent as a QPolygonF
84  QPolygonF thisExtent = mComposerMap->visibleExtentPolygon();
85  //intersect the two
86  QPolygonF intersectExtent = thisExtent.intersected( otherExtent );
87 
88  //setup painter scaling to dots so that raster symbology is drawn to scale
89  double dotsPerMM = painter->device()->logicalDpiX() / 25.4;
90 
91  //setup render context
93  //context units should be in dots
94  ms.setOutputSize( QSizeF( mComposerMap->rect().width() * dotsPerMM, mComposerMap->rect().height() * dotsPerMM ).toSize() );
96  ms.setOutputDpi( painter->device()->logicalDpiX() );
98  context.setForceVectorOutput( true );
99  context.setPainter( painter );
100 
101  painter->save();
102  painter->setCompositionMode( mBlendMode );
103  painter->translate( mComposerMap->mXOffset, mComposerMap->mYOffset );
104  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
105  painter->setRenderHint( QPainter::Antialiasing );
106 
107  mFrameSymbol->startRender( context );
108 
109  //construct a polygon corresponding to the intersecting map extent
110  //need to scale line to dots, rather then mm, since the painter has been scaled to dots
111  QTransform mapTransform;
112  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, dotsPerMM * mComposerMap->rect().width(), dotsPerMM * mComposerMap->rect().height() ) );
113 
114  //workaround QT Bug #21329
115  thisRectPoly.pop_back();
116  thisExtent.pop_back();
117 
118  //create transform from map coordinates to painter coordinates
119  QTransform::quadToQuad( thisExtent, thisRectPoly, mapTransform );
120  QPolygonF intersectPolygon;
121  intersectPolygon = mapTransform.map( intersectExtent );
122 
123  QList<QPolygonF> rings; //empty list
124  if ( !mInverted )
125  {
126  //Render the intersecting map extent
127  mFrameSymbol->renderPolygon( intersectPolygon, &rings, 0, context );;
128  }
129  else
130  {
131  //We are inverting the overview frame (ie, shading outside the intersecting extent)
132  //Construct a polygon corresponding to the overview map extent
133  QPolygonF outerPolygon;
134  outerPolygon << QPointF( 0, 0 )
135  << QPointF( mComposerMap->rect().width() * dotsPerMM, 0 )
136  << QPointF( mComposerMap->rect().width() * dotsPerMM, mComposerMap->rect().height() * dotsPerMM )
137  << QPointF( 0, mComposerMap->rect().height() * dotsPerMM )
138  << QPointF( 0, 0 );
139 
140  //Intersecting extent is an inner ring for the shaded area
141  rings.append( intersectPolygon );
142  mFrameSymbol->renderPolygon( outerPolygon, &rings, 0, context );
143  }
144 
145  mFrameSymbol->stopRender( context );
146  painter->restore();
147 }
148 
149 bool QgsComposerMapOverview::writeXML( QDomElement &elem, QDomDocument &doc ) const
150 {
151  if ( elem.isNull() )
152  {
153  return false;
154  }
155 
156  //overview map frame
157  QDomElement overviewFrameElem = doc.createElement( "ComposerMapOverview" );
158 
159  overviewFrameElem.setAttribute( "frameMap", mFrameMapId );
160  overviewFrameElem.setAttribute( "blendMode", QgsMapRenderer::getBlendModeEnum( mBlendMode ) );
161  overviewFrameElem.setAttribute( "inverted", mInverted );
162  overviewFrameElem.setAttribute( "centered", mCentered );
163 
164  QDomElement frameStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mFrameSymbol, doc );
165  overviewFrameElem.appendChild( frameStyleElem );
166 
167  bool ok = QgsComposerMapItem::writeXML( overviewFrameElem, doc );
168  elem.appendChild( overviewFrameElem );
169  return ok;
170 }
171 
172 bool QgsComposerMapOverview::readXML( const QDomElement &itemElem, const QDomDocument &doc )
173 {
174  Q_UNUSED( doc );
175  if ( itemElem.isNull() )
176  {
177  return false;
178  }
179 
180  bool ok = QgsComposerMapItem::readXML( itemElem, doc );
181 
182  setFrameMap( itemElem.attribute( "frameMap", "-1" ).toInt() );
183  mBlendMode = QgsMapRenderer::getCompositionMode(( QgsMapRenderer::BlendMode ) itemElem.attribute( "blendMode", "0" ).toUInt() );
184  mInverted = ( itemElem.attribute( "inverted", "0" ) != "0" );
185  mCentered = ( itemElem.attribute( "centered", "0" ) != "0" );
186 
187  QDomElement frameStyleElem = itemElem.firstChildElement( "symbol" );
188  if ( !frameStyleElem.isNull() )
189  {
190  delete mFrameSymbol;
191  mFrameSymbol = QgsSymbolLayerV2Utils::loadSymbol<QgsFillSymbolV2>( frameStyleElem );
192  }
193  return ok;
194 }
195 
197 {
198  return mBlendMode != QPainter::CompositionMode_SourceOver;
199 }
200 
201 void QgsComposerMapOverview::setFrameMap( const int mapId )
202 {
203  if ( mFrameMapId == mapId )
204  {
205  //no change
206  return;
207  }
208 
209  //disconnect old map
210  if ( mFrameMapId != -1 && mComposerMap && mComposerMap->composition() )
211  {
212  const QgsComposerMap* map = mComposerMap->composition()->getComposerMapById( mFrameMapId );
213  if ( map )
214  {
215  QObject::disconnect( map, SIGNAL( extentChanged() ), this, SLOT( overviewExtentChanged() ) );
216  }
217  }
218  mFrameMapId = mapId;
219  //connect to new map signals
220  connectSignals();
221 }
222 
224 {
225  if ( !mComposerMap )
226  {
227  return;
228  }
229 
230  if ( mFrameMapId != -1 && mComposerMap->composition() )
231  {
232  const QgsComposerMap* map = mComposerMap->composition()->getComposerMapById( mFrameMapId );
233  if ( map )
234  {
235  QObject::connect( map, SIGNAL( extentChanged() ), this, SLOT( overviewExtentChanged() ) );
236  }
237  }
238 }
239 
241 {
242  delete mFrameSymbol;
243  mFrameSymbol = symbol;
244 }
245 
246 void QgsComposerMapOverview::setBlendMode( const QPainter::CompositionMode blendMode )
247 {
248  mBlendMode = blendMode;
249 }
250 
251 void QgsComposerMapOverview::setInverted( const bool inverted )
252 {
253  mInverted = inverted;
254 }
255 
256 void QgsComposerMapOverview::setCentered( const bool centered )
257 {
258  mCentered = centered;
260 }
261 
263 {
264  if ( !mComposerMap )
265  {
266  return;
267  }
268 
269  //if using overview centering, update the map's extent
270  if ( mComposerMap->composition() && mCentered && mFrameMapId != -1 )
271  {
273 
274  const QgsComposerMap* overviewFrameMap = mComposerMap->composition()->getComposerMapById( mFrameMapId );
275  if ( !overviewFrameMap )
276  {
277  //redraw map so that overview gets updated
278  mComposerMap->update();
279  return;
280  }
281  QgsRectangle otherExtent = *overviewFrameMap->currentMapExtent();
282 
283  QgsPoint center = otherExtent.center();
284  QgsRectangle movedExtent( center.x() - extent.width() / 2,
285  center.y() - extent.height() / 2,
286  center.x() - extent.width() / 2 + extent.width(),
287  center.y() - extent.height() / 2 + extent.height() );
288  *mComposerMap->currentMapExtent() = movedExtent;
289 
290  //trigger a recalculation of data defined extents, scale and rotation, since that
291  //may override the map centering
293 
294  //must invalidate cache so that map gets redrawn
295  mComposerMap->cache();
296  }
297 
298  //repaint map so that overview gets updated
299  mComposerMap->update();
300 }
301 
302 
303 //
304 // QgsComposerMapOverviewStack
305 //
306 
308  : QgsComposerMapItemStack( map )
309 {
310 
311 }
312 
314 {
315 
316 }
317 
319 {
321 }
322 
323 void QgsComposerMapOverviewStack::removeOverview( const QString &overviewId )
324 {
326 }
327 
328 void QgsComposerMapOverviewStack::moveOverviewUp( const QString &overviewId )
329 {
331 }
332 
333 void QgsComposerMapOverviewStack::moveOverviewDown( const QString &overviewId )
334 {
336 }
337 
339 {
341  return dynamic_cast<const QgsComposerMapOverview*>( item );
342 }
343 
345 {
347  return dynamic_cast<QgsComposerMapOverview*>( item );
348 }
349 
351 {
353  return dynamic_cast<QgsComposerMapOverview*>( item );
354 }
355 
357 {
360  return *overview;
361 }
362 
363 QList<QgsComposerMapOverview *> QgsComposerMapOverviewStack::asList() const
364 {
365  QList< QgsComposerMapOverview* > list;
366  QList< QgsComposerMapItem* >::const_iterator it = mItems.begin();
367  for ( ; it != mItems.end(); ++it )
368  {
369  QgsComposerMapOverview* overview = dynamic_cast<QgsComposerMapOverview*>( *it );
370  if ( overview )
371  {
372  list.append( overview );
373  }
374  }
375  return list;
376 }
377 
378 bool QgsComposerMapOverviewStack::readXML( const QDomElement &elem, const QDomDocument &doc )
379 {
380  removeItems();
381 
382  //read overview stack
383  QDomNodeList mapOverviewNodeList = elem.elementsByTagName( "ComposerMapOverview" );
384  for ( int i = 0; i < mapOverviewNodeList.size(); ++i )
385  {
386  QDomElement mapOverviewElem = mapOverviewNodeList.at( i ).toElement();
387  QgsComposerMapOverview* mapOverview = new QgsComposerMapOverview( mapOverviewElem.attribute( "name" ), mComposerMap );
388  mapOverview->readXML( mapOverviewElem, doc );
389  mItems.append( mapOverview );
390  }
391 
392  return true;
393 }