QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgshighlight.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgshighlight.cpp - widget to highlight features on the map
3  --------------------------------------
4  Date : 02-03-2011
5  Copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH
6  Email : jef at norbit dot de
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 <QImage>
17 
18 #include "qgsmarkersymbollayerv2.h"
19 #include "qgslinesymbollayerv2.h"
20 
21 #include "qgscoordinatetransform.h"
22 #include "qgsfillsymbollayerv2.h"
23 #include "qgsgeometry.h"
24 #include "qgshighlight.h"
25 #include "qgslinesymbollayerv2.h"
26 #include "qgslinesymbollayerv2.h"
27 #include "qgsmapcanvas.h"
28 #include "qgsmaplayer.h"
29 #include "qgsmaprenderer.h"
30 #include "qgsmarkersymbollayerv2.h"
31 #include "qgsrendercontext.h"
32 #include "qgssymbollayerv2.h"
33 #include "qgssymbolv2.h"
34 #include "qgsvectorlayer.h"
35 
36 /* Few notes about highligting (RB):
37  - The highlight fill must always be partially transparent because above highlighted layer
38  may be another layer which must remain partially visible.
39  - Because single highlight color does not work well with layers using similar layer color
40  there were considered various possibilities but no optimal solution was found.
41  What does not work:
42  - lighter/darker color: it would work more or less for fully opaque highlight, but
43  overlaying transparent lighter color over original has small visual efect.
44  - complemetary color: mixing transparent (128) complement color with original color
45  results in grey for all colors
46  - contrast line style/ fill pattern: impression is not highligh but just different style
47  - line buffer with contrast (or 2 contrast) color: the same as with patterns, no highlight impression
48  - fill with highlight or contrast color but opaque and using pattern
49  (e.g. Qt::Dense7Pattern): again no highlight impression
50 */
57  : QgsMapCanvasItem( mapCanvas )
58  , mLayer( layer )
59  , mBuffer( 0 )
60  , mMinWidth( 0 )
61 {
62  mGeometry = geom ? new QgsGeometry( *geom ) : 0;
63  init();
64 }
65 
67  : QgsMapCanvasItem( mapCanvas )
68  , mLayer( static_cast<QgsMapLayer *>( layer ) )
69  , mBuffer( 0 )
70  , mMinWidth( 0 )
71 {
72  mGeometry = geom ? new QgsGeometry( *geom ) : 0;
73  init();
74 }
75 
77  : QgsMapCanvasItem( mapCanvas )
78  , mGeometry( 0 )
79  , mLayer( static_cast<QgsMapLayer *>( layer ) )
80  , mFeature( feature )
81  , mBuffer( 0 )
82  , mMinWidth( 0 )
83 {
84  init();
85 }
86 
87 void QgsHighlight::init()
88 {
90  {
92  if ( ct )
93  {
94  if ( mGeometry )
95  {
96  mGeometry->transform( *ct );
97  }
98  else if ( mFeature.geometry() )
99  {
100  mFeature.geometry()->transform( *ct );
101  }
102  }
103  }
104  updateRect();
105  update();
106  setColor( QColor( Qt::lightGray ) );
107 }
108 
110 {
111  delete mGeometry;
112 }
113 
117 void QgsHighlight::setColor( const QColor & color )
118 {
119  mPen.setColor( color );
120  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
121  mBrush.setColor( fillColor );
122  mBrush.setStyle( Qt::SolidPattern );
123 }
124 
125 void QgsHighlight::setFillColor( const QColor & fillColor )
126 {
127  mBrush.setColor( fillColor );
128  mBrush.setStyle( Qt::SolidPattern );
129 }
130 
131 QgsFeatureRendererV2 * QgsHighlight::getRenderer( const QgsRenderContext & context, const QColor & color, const QColor & fillColor )
132 {
133  QgsFeatureRendererV2 *renderer = 0;
134  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer*>( mLayer );
135  if ( layer && layer->rendererV2() )
136  {
137  renderer = layer->rendererV2()->clone();
138  }
139  if ( renderer )
140  {
141  foreach ( QgsSymbolV2* symbol, renderer->symbols() )
142  {
143  if ( !symbol ) continue;
144  setSymbol( symbol, context, color, fillColor );
145  }
146  }
147  return renderer;
148 }
149 
150 void QgsHighlight::setSymbol( QgsSymbolV2* symbol, const QgsRenderContext & context, const QColor & color, const QColor & fillColor )
151 {
152  if ( !symbol ) return;
153 
154 
155  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
156  {
157  QgsSymbolLayerV2* symbolLayer = symbol->symbolLayer( i );
158  if ( !symbolLayer ) continue;
159 
160  if ( symbolLayer->subSymbol() )
161  {
162  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
163  }
164  else
165  {
166  symbolLayer->setColor( color ); // line symbology layers
167  symbolLayer->setOutlineColor( color ); // marker and fill symbology layers
168  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
169 
170  // Data defined widths overwrite what we set here (widths do not work with data defined)
171  QgsSimpleMarkerSymbolLayerV2 * simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayerV2*>( symbolLayer );
172  if ( simpleMarker )
173  {
174  simpleMarker->setOutlineWidth( getSymbolWidth( context, simpleMarker->outlineWidth(), simpleMarker->outlineWidthUnit() ) );
175  }
176  QgsSimpleLineSymbolLayerV2 * simpleLine = dynamic_cast<QgsSimpleLineSymbolLayerV2*>( symbolLayer );
177  if ( simpleLine )
178  {
179  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
180  }
181  QgsSimpleFillSymbolLayerV2 * simpleFill = dynamic_cast<QgsSimpleFillSymbolLayerV2*>( symbolLayer );
182  if ( simpleFill )
183  {
184  simpleFill->setBorderWidth( getSymbolWidth( context, simpleFill->borderWidth(), simpleFill->outputUnit() ) );
185  }
186  symbolLayer->removeDataDefinedProperty( "color" );
187  symbolLayer->removeDataDefinedProperty( "color_border" );
188  }
189  }
190 }
191 
192 double QgsHighlight::getSymbolWidth( const QgsRenderContext & context, double width, QgsSymbolV2::OutputUnit unit )
193 {
194  // if necessary scale mm to map units
195  double scale = 1.;
196  if ( unit == QgsSymbolV2::MapUnit )
197  {
199  }
200  width = qMax( width + 2 * mBuffer * scale, mMinWidth * scale );
201  return width;
202 }
203 
207 void QgsHighlight::setWidth( int width )
208 {
209  mPen.setWidth( width );
210 }
211 
212 void QgsHighlight::paintPoint( QPainter *p, QgsPoint point )
213 {
214  QPolygonF r( 5 );
215 
216  double d = mMapCanvas->extent().width() * 0.005;
217  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
218  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
219  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
220  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
221  r[4] = r[0];
222 
223  p->drawPolygon( r );
224 }
225 
226 void QgsHighlight::paintLine( QPainter *p, QgsPolyline line )
227 {
228  QPolygonF polygon( line.size() );
229 
230  for ( int i = 0; i < line.size(); i++ )
231  {
232  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
233  }
234 
235  p->drawPolyline( polygon );
236 }
237 
238 void QgsHighlight::paintPolygon( QPainter *p, QgsPolygon polygon )
239 {
240  // OddEven fill rule by default
241  QPainterPath path;
242 
243  p->setPen( mPen );
244  p->setBrush( mBrush );
245 
246  for ( int i = 0; i < polygon.size(); i++ )
247  {
248  if ( polygon[i].empty() ) continue;
249 
250  QPolygonF ring;
251  ring.reserve( polygon[i].size() + 1 );
252 
253  for ( int j = 0; j < polygon[i].size(); j++ )
254  {
255  //adding point only if it is more than a pixel appart from the previous one
256  const QPointF cur = toCanvasCoordinates( polygon[i][j] ) - pos();
257  if ( 0 == j || std::abs( ring.back().x() - cur.x() ) > 1 || std::abs( ring.back().y() - cur.y() ) > 1 )
258  {
259  ring.push_back( cur );
260  }
261  }
262 
263  ring.push_back( ring[ 0 ] );
264 
265  path.addPolygon( ring );
266  }
267 
268  p->drawPath( path );
269 }
270 
272 {
273  // nothing to do here...
274 }
275 
279 void QgsHighlight::paint( QPainter* p )
280 {
281  if ( mGeometry )
282  {
283  p->setPen( mPen );
284  p->setBrush( mBrush );
285 
286  switch ( mGeometry->wkbType() )
287  {
288  case QGis::WKBPoint:
289  case QGis::WKBPoint25D:
290  {
291  paintPoint( p, mGeometry->asPoint() );
292  }
293  break;
294 
295  case QGis::WKBMultiPoint:
297  {
298  QgsMultiPoint m = mGeometry->asMultiPoint();
299  for ( int i = 0; i < m.size(); i++ )
300  {
301  paintPoint( p, m[i] );
302  }
303  }
304  break;
305 
306  case QGis::WKBLineString:
308  {
309  paintLine( p, mGeometry->asPolyline() );
310  }
311  break;
312 
315  {
316  QgsMultiPolyline m = mGeometry->asMultiPolyline();
317 
318  for ( int i = 0; i < m.size(); i++ )
319  {
320  paintLine( p, m[i] );
321  }
322  }
323  break;
324 
325  case QGis::WKBPolygon:
326  case QGis::WKBPolygon25D:
327  {
328  paintPolygon( p, mGeometry->asPolygon() );
329  }
330  break;
331 
334  {
335  QgsMultiPolygon m = mGeometry->asMultiPolygon();
336  for ( int i = 0; i < m.size(); i++ )
337  {
338  paintPolygon( p, m[i] );
339  }
340  }
341  break;
342 
343  case QGis::WKBUnknown:
344  default:
345  return;
346  }
347  }
348  else if ( mFeature.geometry() )
349  {
350  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer*>( mLayer );
351  if ( !layer )
352  return;
353  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
354  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
355 
356  // Because lower level outlines must be covered by upper level fill color
357  // we render first with temporary opaque color, which is then replaced
358  // by final transparent fill color.
359  QColor tmpColor( 255, 0, 0, 255 );
360  QColor tmpFillColor( 0, 255, 0, 255 );
361 
362  QgsFeatureRendererV2 *renderer = getRenderer( context, tmpColor, tmpFillColor );
363  if ( layer && renderer )
364  {
365 
366  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
367  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
368  image.fill( 0 );
369  QPainter *imagePainter = new QPainter( &image );
370  imagePainter->setRenderHint( QPainter::Antialiasing, true );
371 
372  context.setPainter( imagePainter );
373 
374  renderer->startRender( context, layer->pendingFields() );
375  renderer->renderFeature( mFeature, context );
376  renderer->stopRender( context );
377 
378  imagePainter->end();
379 
380  QColor color( mPen.color() ); // true output color
381  // coefficient to subtract alpha using green (temporary fill)
382  double k = ( 255. - mBrush.color().alpha() ) / 255.;
383  for ( int r = 0; r < image.height(); r++ )
384  {
385  for ( int c = 0; c < image.width(); c++ )
386  {
387  QRgb rgba = image.pixel( c, r );
388  int alpha = qAlpha( rgba );
389  if ( alpha > 0 )
390  {
391  int green = qGreen( rgba );
392  color.setAlpha( qBound<int>( 0, alpha - ( green * k ), 255 ) );
393 
394  image.setPixel( c, r, color.rgba() );
395  }
396  }
397  }
398 
399  p->drawImage( 0, 0, image );
400 
401  delete imagePainter;
402  delete renderer;
403  }
404  }
405 }
406 
408 {
409  if ( mGeometry )
410  {
411  QgsRectangle r = mGeometry->boundingBox();
412 
413  if ( r.isEmpty() )
414  {
415  double d = mMapCanvas->extent().width() * 0.005;
416  r.setXMinimum( r.xMinimum() - d );
417  r.setYMinimum( r.yMinimum() - d );
418  r.setXMaximum( r.xMaximum() + d );
419  r.setYMaximum( r.yMaximum() + d );
420  }
421 
422  setRect( r );
423  setVisible( mGeometry );
424  }
425  else if ( mFeature.geometry() )
426  {
427  // We are currently using full map canvas extent for two reasons:
428  // 1) currently there is no method in QgsFeatureRendererV2 to get rendered feature
429  // bounding box
430  // 2) using different extent would result in shifted fill patterns
431 
432  // This is an hack to pass QgsMapCanvasItem::setRect what it
433  // expects (encoding of position and size of the item)
435  QgsPoint topLeft = m2p.toMapPoint( 0, 0 );
436  double res = m2p.mapUnitsPerPixel();
437  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
438  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
439  setRect( rect );
440 
441  setVisible( true );
442  }
443  else
444  {
445  setRect( QgsRectangle() );
446  }
447 }