QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules 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 "qgsmapcanvas.h"
26 #include "qgsmaplayer.h"
27 #include "qgsmaprenderer.h"
28 #include "qgsrendercontext.h"
29 #include "qgssymbollayerv2.h"
30 #include "qgssymbolv2.h"
31 #include "qgsvectorlayer.h"
32 
33 /* Few notes about highligting (RB):
34  - The highlight fill must always be partially transparent because above highlighted layer
35  may be another layer which must remain partially visible.
36  - Because single highlight color does not work well with layers using similar layer color
37  there were considered various possibilities but no optimal solution was found.
38  What does not work:
39  - lighter/darker color: it would work more or less for fully opaque highlight, but
40  overlaying transparent lighter color over original has small visual efect.
41  - complemetary color: mixing transparent (128) complement color with original color
42  results in grey for all colors
43  - contrast line style/ fill pattern: impression is not highligh but just different style
44  - line buffer with contrast (or 2 contrast) color: the same as with patterns, no highlight impression
45  - fill with highlight or contrast color but opaque and using pattern
46  (e.g. Qt::Dense7Pattern): again no highlight impression
47 */
54  : QgsMapCanvasItem( mapCanvas )
55  , mLayer( layer )
56  , mBuffer( 0 )
57  , mMinWidth( 0 )
58 {
59  mGeometry = geom ? new QgsGeometry( *geom ) : 0;
60  init();
61 }
62 
64  : QgsMapCanvasItem( mapCanvas )
65  , mLayer( static_cast<QgsMapLayer *>( layer ) )
66  , mBuffer( 0 )
67  , mMinWidth( 0 )
68 {
69  mGeometry = geom ? new QgsGeometry( *geom ) : 0;
70  init();
71 }
72 
74  : QgsMapCanvasItem( mapCanvas )
75  , mGeometry( 0 )
76  , mLayer( static_cast<QgsMapLayer *>( layer ) )
77  , mFeature( feature )
78  , mBuffer( 0 )
79  , mMinWidth( 0 )
80 {
81  init();
82 }
83 
84 void QgsHighlight::init()
85 {
87  {
89  if ( ct )
90  {
91  if ( mGeometry )
92  {
93  mGeometry->transform( *ct );
94  }
95  else if ( mFeature.geometry() )
96  {
97  mFeature.geometry()->transform( *ct );
98  }
99  }
100  }
101  updateRect();
102  update();
103  setColor( QColor( Qt::lightGray ) );
104 }
105 
107 {
108  delete mGeometry;
109 }
110 
114 void QgsHighlight::setColor( const QColor & color )
115 {
116  mPen.setColor( color );
117  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
118  mBrush.setColor( fillColor );
119  mBrush.setStyle( Qt::SolidPattern );
120 }
121 
122 void QgsHighlight::setFillColor( const QColor & fillColor )
123 {
124  mBrush.setColor( fillColor );
125  mBrush.setStyle( Qt::SolidPattern );
126 }
127 
128 QgsFeatureRendererV2 * QgsHighlight::getRenderer( const QgsRenderContext & context, const QColor & color, const QColor & fillColor )
129 {
130  QgsFeatureRendererV2 *renderer = 0;
131  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer*>( mLayer );
132  if ( layer && layer->rendererV2() )
133  {
134  renderer = layer->rendererV2()->clone();
135  }
136  if ( renderer )
137  {
138  foreach ( QgsSymbolV2* symbol, renderer->symbols() )
139  {
140  if ( !symbol ) continue;
141  setSymbol( symbol, context, color, fillColor );
142  }
143  }
144  return renderer;
145 }
146 
147 void QgsHighlight::setSymbol( QgsSymbolV2* symbol, const QgsRenderContext & context, const QColor & color, const QColor & fillColor )
148 {
149  if ( !symbol ) return;
150 
151 
152  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
153  {
154  QgsSymbolLayerV2* symbolLayer = symbol->symbolLayer( i );
155  if ( !symbolLayer ) continue;
156 
157  if ( symbolLayer->subSymbol() )
158  {
159  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
160  }
161  else
162  {
163  symbolLayer->setColor( color ); // line symbology layers
164  symbolLayer->setOutlineColor( color ); // marker and fill symbology layers
165  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
166 
167  // Data defined widths overwrite what we set here (widths do not work with data defined)
168  QgsSimpleMarkerSymbolLayerV2 * simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayerV2*>( symbolLayer );
169  if ( simpleMarker )
170  {
171  simpleMarker->setOutlineWidth( getSymbolWidth( context, simpleMarker->outlineWidth(), simpleMarker->outlineWidthUnit() ) );
172  }
173  QgsSimpleLineSymbolLayerV2 * simpleLine = dynamic_cast<QgsSimpleLineSymbolLayerV2*>( symbolLayer );
174  if ( simpleLine )
175  {
176  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
177  }
178  QgsSimpleFillSymbolLayerV2 * simpleFill = dynamic_cast<QgsSimpleFillSymbolLayerV2*>( symbolLayer );
179  if ( simpleFill )
180  {
181  simpleFill->setBorderWidth( getSymbolWidth( context, simpleFill->borderWidth(), simpleFill->outputUnit() ) );
182  }
183  symbolLayer->removeDataDefinedProperty( "color" );
184  symbolLayer->removeDataDefinedProperty( "color_border" );
185  }
186  }
187 }
188 
189 double QgsHighlight::getSymbolWidth( const QgsRenderContext & context, double width, QgsSymbolV2::OutputUnit unit )
190 {
191  // if necessary scale mm to map units
192  double scale = 1.;
193  if ( unit == QgsSymbolV2::MapUnit )
194  {
196  }
197  width = qMax( width + 2 * mBuffer * scale, mMinWidth * scale );
198  return width;
199 }
200 
204 void QgsHighlight::setWidth( int width )
205 {
206  mPen.setWidth( width );
207 }
208 
209 void QgsHighlight::paintPoint( QPainter *p, QgsPoint point )
210 {
211  QPolygonF r( 5 );
212 
213  double d = mMapCanvas->extent().width() * 0.005;
214  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
215  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
216  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
217  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
218  r[4] = r[0];
219 
220  p->drawPolygon( r );
221 }
222 
223 void QgsHighlight::paintLine( QPainter *p, QgsPolyline line )
224 {
225  QPolygonF polygon( line.size() );
226 
227  for ( int i = 0; i < line.size(); i++ )
228  {
229  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
230  }
231 
232  p->drawPolyline( polygon );
233 }
234 
235 void QgsHighlight::paintPolygon( QPainter *p, QgsPolygon polygon )
236 {
237  // OddEven fill rule by default
238  QPainterPath path;
239 
240  p->setPen( mPen );
241  p->setBrush( mBrush );
242 
243  for ( int i = 0; i < polygon.size(); i++ )
244  {
245  if ( polygon[i].empty() ) continue;
246 
247  QPolygonF ring;
248  ring.reserve( polygon[i].size() + 1 );
249 
250  for ( int j = 0; j < polygon[i].size(); j++ )
251  {
252  //adding point only if it is more than a pixel appart from the previous one
253  const QPointF cur = toCanvasCoordinates( polygon[i][j] ) - pos();
254  if ( 0 == j || std::abs( ring.back().x() - cur.x() ) > 1 || std::abs( ring.back().y() - cur.y() ) > 1 )
255  {
256  ring.push_back( cur );
257  }
258  }
259 
260  ring.push_back( ring[ 0 ] );
261 
262  path.addPolygon( ring );
263  }
264 
265  p->drawPath( path );
266 }
267 
269 {
270  // nothing to do here...
271 }
272 
276 void QgsHighlight::paint( QPainter* p )
277 {
278  if ( mGeometry )
279  {
280  p->setPen( mPen );
281  p->setBrush( mBrush );
282 
283  switch ( mGeometry->wkbType() )
284  {
285  case QGis::WKBPoint:
286  case QGis::WKBPoint25D:
287  {
288  paintPoint( p, mGeometry->asPoint() );
289  }
290  break;
291 
292  case QGis::WKBMultiPoint:
294  {
295  QgsMultiPoint m = mGeometry->asMultiPoint();
296  for ( int i = 0; i < m.size(); i++ )
297  {
298  paintPoint( p, m[i] );
299  }
300  }
301  break;
302 
303  case QGis::WKBLineString:
305  {
306  paintLine( p, mGeometry->asPolyline() );
307  }
308  break;
309 
312  {
313  QgsMultiPolyline m = mGeometry->asMultiPolyline();
314 
315  for ( int i = 0; i < m.size(); i++ )
316  {
317  paintLine( p, m[i] );
318  }
319  }
320  break;
321 
322  case QGis::WKBPolygon:
323  case QGis::WKBPolygon25D:
324  {
325  paintPolygon( p, mGeometry->asPolygon() );
326  }
327  break;
328 
331  {
332  QgsMultiPolygon m = mGeometry->asMultiPolygon();
333  for ( int i = 0; i < m.size(); i++ )
334  {
335  paintPolygon( p, m[i] );
336  }
337  }
338  break;
339 
340  case QGis::WKBUnknown:
341  default:
342  return;
343  }
344  }
345  else if ( mFeature.geometry() )
346  {
347  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer*>( mLayer );
348  if ( !layer )
349  return;
350  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
351  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
352 
353  // Because lower level outlines must be covered by upper level fill color
354  // we render first with temporary opaque color, which is then replaced
355  // by final transparent fill color.
356  QColor tmpColor( 255, 0, 0, 255 );
357  QColor tmpFillColor( 0, 255, 0, 255 );
358 
359  QgsFeatureRendererV2 *renderer = getRenderer( context, tmpColor, tmpFillColor );
360  if ( layer && renderer )
361  {
362 
363  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
364  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
365  image.fill( 0 );
366  QPainter *imagePainter = new QPainter( &image );
367  imagePainter->setRenderHint( QPainter::Antialiasing, true );
368 
369  context.setPainter( imagePainter );
370 
371  renderer->startRender( context, layer->pendingFields() );
372  renderer->renderFeature( mFeature, context );
373  renderer->stopRender( context );
374 
375  imagePainter->end();
376 
377  QColor color( mPen.color() ); // true output color
378  // coefficient to subtract alpha using green (temporary fill)
379  double k = ( 255. - mBrush.color().alpha() ) / 255.;
380  for ( int r = 0; r < image.height(); r++ )
381  {
382  for ( int c = 0; c < image.width(); c++ )
383  {
384  QRgb rgba = image.pixel( c, r );
385  int alpha = qAlpha( rgba );
386  if ( alpha > 0 )
387  {
388  int green = qGreen( rgba );
389  color.setAlpha( qBound<int>( 0, alpha - ( green * k ), 255 ) );
390 
391  image.setPixel( c, r, color.rgba() );
392  }
393  }
394  }
395 
396  p->drawImage( 0, 0, image );
397 
398  delete imagePainter;
399  delete renderer;
400  }
401  }
402 }
403 
405 {
406  if ( mGeometry )
407  {
408  QgsRectangle r = mGeometry->boundingBox();
409 
410  if ( r.isEmpty() )
411  {
412  double d = mMapCanvas->extent().width() * 0.005;
413  r.setXMinimum( r.xMinimum() - d );
414  r.setYMinimum( r.yMinimum() - d );
415  r.setXMaximum( r.xMaximum() + d );
416  r.setYMaximum( r.yMaximum() + d );
417  }
418 
419  setRect( r );
420  setVisible( mGeometry );
421  }
422  else if ( mFeature.geometry() )
423  {
424  // We are currently using full map canvas extent for two reasons:
425  // 1) currently there is no method in QgsFeatureRendererV2 to get rendered feature
426  // bounding box
427  // 2) using different extent would result in shifted fill patterns
428 
429  // This is an hack to pass QgsMapCanvasItem::setRect what it
430  // expects (encoding of position and size of the item)
432  QgsPoint topLeft = m2p.toMapPoint( 0, 0 );
433  double res = m2p.mapUnitsPerPixel();
434  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
435  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
436  setRect( rect );
437 
438  setVisible( true );
439  }
440  else
441  {
442  setRect( QgsRectangle() );
443  }
444 }
QgsFeatureRendererV2 * rendererV2()
Return renderer V2.
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:49
bool isEmpty() const
test if rectangle is empty.
virtual void updatePosition() override
called on changed extent or resize event to update position of the item
virtual double width() const
virtual void setOutlineColor(const QColor &color)
Set outline color.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:163
QPointF toCanvasCoordinates(const QgsPoint &point) const
transformation from map coordinates to screen coordinates
const QgsCoordinateTransform * layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer's CRS to destination CRS.
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:188
QgsMultiPolyline asMultiPolyline() const
return contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list
QVector< QgsPoint > QgsPolyline
polyline is represented as a vector of points
Definition: qgsgeometry.h:33
QgsGeometry * geometry() const
Get the geometry object associated with this feature.
Definition: qgsfeature.cpp:112
QgsPolygon asPolygon() const
return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list ...
void setFillColor(const QColor &fillColor)
Set polygons fill color.
An abstract class for items that can be placed on the map canvas.
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
const QgsMapToPixel & mapToPixel() const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
QgsRectangle rect() const
returns canvas item rectangle in map units
virtual void removeDataDefinedProperty(const QString &property)
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
double x() const
Definition: qgspoint.h:126
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)=0
QgsMultiPolygon asMultiPolygon() const
return contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty ...
The QgsMapSettings class contains configuration for rendering of the map.
virtual void stopRender(QgsRenderContext &context)=0
virtual QgsSymbolV2List symbols()=0
for symbol levels
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:34
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false)
virtual QgsFeatureRendererV2 * clone() const =0
QgsHighlight(QgsMapCanvas *mapCanvas, QgsGeometry *geom, QgsMapLayer *layer)
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:193
QSize outputSize() const
Return the size of the resulting map image.
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:178
QgsSymbolV2::OutputUnit outputUnit() const override
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:168
virtual void setWidth(double width)
void updateRect()
recalculates needed rectangle
void setRect(const QgsRectangle &r, bool resetRotation=true)
sets canvas item rectangle in map units
QVector< QgsPolygon > QgsMultiPolygon
a collection of QgsPolygons that share a common collection of attributes
Definition: qgsgeometry.h:48
QVector< QgsPoint > QgsMultiPoint
a collection of QgsPoints that share a common collection of attributes
Definition: qgsgeometry.h:42
int symbolLayerCount()
Returns total number of symbol layers contained in the symbol.
Definition: qgssymbolv2.h:112
void setPainter(QPainter *p)
QGis::WkbType wkbType() const
Returns type of wkb (point / linestring / polygon etc.)
double mapUnitsPerPixel() const
Return current map units per pixel.
QVector< QgsPolyline > QgsPolygon
polygon: first item of the list is outer ring, inner rings (if any) start from second item ...
Definition: qgsgeometry.h:39
const QgsMapLayer * layer() const
Definition: qgshighlight.h:69
A class to represent a point.
Definition: qgspoint.h:63
virtual void setFillColor(const QColor &color)
Set fill color.
A class to represent a vector.
Definition: qgspoint.h:32
QVector< QgsPolyline > QgsMultiPolyline
a collection of QgsPolylines that share a common collection of attributes
Definition: qgsgeometry.h:45
virtual QgsSymbolV2 * subSymbol()
QgsPolyline asPolyline() const
return contents of the geometry as a polyline if wkbType is WKBLineString, otherwise an empty list ...
QgsSymbolV2::OutputUnit outlineWidthUnit() const
QgsRectangle boundingBox()
Returns the bounding box of this feature.
Contains information about the context of a rendering operation.
static double lineWidthScaleFactor(const QgsRenderContext &c, QgsSymbolV2::OutputUnit u, const QgsMapUnitScale &scale=QgsMapUnitScale())
Returns the line width scale factor depending on the unit and the paint device.
void setColor(const QColor &color)
Set line/outline to color, polygon fill to color with alpha = 63.
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:173
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QgsMultiPoint asMultiPoint() const
return contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty list...
QgsMapCanvas * mMapCanvas
pointer to map canvas
virtual void paint(QPainter *p) override
Class for doing transforms between two map coordinate systems.
int transform(const QgsCoordinateTransform &ct)
Transform this geometry as described by CoordinateTranasform ct.
double y() const
Definition: qgspoint.h:134
virtual void setColor(const QColor &color)
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
QgsSymbolLayerV2 * symbolLayer(int layer)
Returns a specific symbol layers contained in the symbol.
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:198
QgsPoint asPoint() const
return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
Represents a vector layer which manages a vector based data sets.
double size
Definition: qgssvgcache.cpp:77
void setBorderWidth(double borderWidth)
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:183
QgsPoint toMapPoint(qreal x, qreal y) const
QgsSymbolV2::OutputUnit widthUnit() const
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:158
void setWidth(int width)
Set width.