QGIS API Documentation  2.99.0-Master (009e47e)
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 "qgsmarkersymbollayer.h"
19 #include "qgslinesymbollayer.h"
20 
21 #include "qgscoordinatetransform.h"
22 #include "qgsfillsymbollayer.h"
23 #include "qgsgeometry.h"
24 #include "qgshighlight.h"
25 #include "qgsmapcanvas.h"
26 #include "qgsmaplayer.h"
27 #include "qgsrendercontext.h"
28 #include "qgssymbollayer.h"
29 #include "qgssymbol.h"
30 #include "qgsvectorlayer.h"
31 #include "qgsrenderer.h"
32 
33 /* Few notes about highlighting (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 effect.
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 */
48 
50  : QgsMapCanvasItem( mapCanvas )
51  , mLayer( layer )
52  , mBuffer( 0 )
53  , mMinWidth( 0 )
54 {
55  mGeometry = !geom.isNull() ? new QgsGeometry( geom ) : nullptr;
56  init();
57 }
58 
60  : QgsMapCanvasItem( mapCanvas )
61  , mLayer( static_cast<QgsMapLayer *>( layer ) )
62  , mBuffer( 0 )
63  , mMinWidth( 0 )
64 {
65  mGeometry = !geom.isNull() ? new QgsGeometry( geom ) : nullptr;
66  init();
67 }
68 
70  : QgsMapCanvasItem( mapCanvas )
71  , mGeometry( nullptr )
72  , mLayer( static_cast<QgsMapLayer *>( layer ) )
73  , mFeature( feature )
74  , mBuffer( 0 )
75  , mMinWidth( 0 )
76 {
77  init();
78 }
79 
80 void QgsHighlight::init()
81 {
83  if ( ct.isValid() )
84  {
85  if ( mGeometry )
86  {
87  mGeometry->transform( ct );
88  }
89  else if ( mFeature.hasGeometry() )
90  {
91  QgsGeometry g = mFeature.geometry();
92  g.transform( ct );
93  mFeature.setGeometry( g );
94  }
95  }
96 
97  updateRect();
98  update();
99  setColor( QColor( Qt::lightGray ) );
100 }
101 
103 {
104  delete mGeometry;
105 }
106 
107 void QgsHighlight::setColor( const QColor &color )
108 {
109  mPen.setColor( color );
110  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
111  mBrush.setColor( fillColor );
112  mBrush.setStyle( Qt::SolidPattern );
113 }
114 
115 void QgsHighlight::setFillColor( const QColor &fillColor )
116 {
117  mBrush.setColor( fillColor );
118  mBrush.setStyle( Qt::SolidPattern );
119 }
120 
121 QgsFeatureRenderer *QgsHighlight::getRenderer( QgsRenderContext &context, const QColor &color, const QColor &fillColor )
122 {
123  QgsFeatureRenderer *renderer = nullptr;
124  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
125  if ( layer && layer->renderer() )
126  {
127  renderer = layer->renderer()->clone();
128  }
129  if ( renderer )
130  {
131  Q_FOREACH ( QgsSymbol *symbol, renderer->symbols( context ) )
132  {
133  if ( !symbol ) continue;
134  setSymbol( symbol, context, color, fillColor );
135  }
136  }
137  return renderer;
138 }
139 
140 void QgsHighlight::setSymbol( QgsSymbol *symbol, const QgsRenderContext &context, const QColor &color, const QColor &fillColor )
141 {
142  if ( !symbol ) return;
143 
144 
145  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
146  {
147  QgsSymbolLayer *symbolLayer = symbol->symbolLayer( i );
148  if ( !symbolLayer ) continue;
149 
150  if ( symbolLayer->subSymbol() )
151  {
152  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
153  }
154  else
155  {
156  symbolLayer->setColor( color ); // line symbology layers
157  symbolLayer->setStrokeColor( color ); // marker and fill symbology layers
158  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
159 
160  // Data defined widths overwrite what we set here (widths do not work with data defined)
161  QgsSimpleMarkerSymbolLayer *simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayer *>( symbolLayer );
162  if ( simpleMarker )
163  {
164  simpleMarker->setStrokeWidth( getSymbolWidth( context, simpleMarker->strokeWidth(), simpleMarker->strokeWidthUnit() ) );
165  }
166  QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast<QgsSimpleLineSymbolLayer *>( symbolLayer );
167  if ( simpleLine )
168  {
169  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
170  }
171  QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast<QgsSimpleFillSymbolLayer *>( symbolLayer );
172  if ( simpleFill )
173  {
174  simpleFill->setStrokeWidth( getSymbolWidth( context, simpleFill->strokeWidth(), simpleFill->outputUnit() ) );
175  }
178  }
179  }
180 }
181 
182 double QgsHighlight::getSymbolWidth( const QgsRenderContext &context, double width, QgsUnitTypes::RenderUnit unit )
183 {
184  // if necessary scale mm to map units
185  double scale = 1.;
186  if ( unit == QgsUnitTypes::RenderMapUnits )
187  {
189  }
190  width = qMax( width + 2 * mBuffer * scale, mMinWidth * scale );
191  return width;
192 }
193 
194 void QgsHighlight::setWidth( int width )
195 {
196  mPen.setWidth( width );
197 }
198 
199 void QgsHighlight::paintPoint( QPainter *p, const QgsPointXY &point )
200 {
201  QPolygonF r( 5 );
202 
203  double d = mMapCanvas->extent().width() * 0.005;
204  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
205  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
206  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
207  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
208  r[4] = r[0];
209 
210  p->drawPolygon( r );
211 }
212 
213 void QgsHighlight::paintLine( QPainter *p, QgsPolyline line )
214 {
215  QPolygonF polygon( line.size() );
216 
217  for ( int i = 0; i < line.size(); i++ )
218  {
219  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
220  }
221 
222  p->drawPolyline( polygon );
223 }
224 
225 void QgsHighlight::paintPolygon( QPainter *p, QgsPolygon polygon )
226 {
227  // OddEven fill rule by default
228  QPainterPath path;
229 
230  p->setPen( mPen );
231  p->setBrush( mBrush );
232 
233  for ( int i = 0; i < polygon.size(); i++ )
234  {
235  if ( polygon[i].empty() ) continue;
236 
237  QPolygonF ring;
238  ring.reserve( polygon[i].size() + 1 );
239 
240  for ( int j = 0; j < polygon[i].size(); j++ )
241  {
242  //adding point only if it is more than a pixel apart from the previous one
243  const QPointF cur = toCanvasCoordinates( polygon[i][j] ) - pos();
244  if ( 0 == j || std::abs( ring.back().x() - cur.x() ) > 1 || std::abs( ring.back().y() - cur.y() ) > 1 )
245  {
246  ring.push_back( cur );
247  }
248  }
249 
250  ring.push_back( ring[ 0 ] );
251 
252  path.addPolygon( ring );
253  }
254 
255  p->drawPath( path );
256 }
257 
259 {
260  // nothing to do here...
261 }
262 
263 void QgsHighlight::paint( QPainter *p )
264 {
265  if ( mGeometry )
266  {
267  p->setPen( mPen );
268  p->setBrush( mBrush );
269 
270  switch ( mGeometry->type() )
271  {
273  {
274  if ( !mGeometry->isMultipart() )
275  {
276  paintPoint( p, mGeometry->asPoint() );
277  }
278  else
279  {
280  QgsMultiPoint m = mGeometry->asMultiPoint();
281  for ( int i = 0; i < m.size(); i++ )
282  {
283  paintPoint( p, m[i] );
284  }
285  }
286  }
287  break;
288 
290  {
291  if ( !mGeometry->isMultipart() )
292  {
293  paintLine( p, mGeometry->asPolyline() );
294  }
295  else
296  {
297  QgsMultiPolyline m = mGeometry->asMultiPolyline();
298 
299  for ( int i = 0; i < m.size(); i++ )
300  {
301  paintLine( p, m[i] );
302  }
303  }
304  break;
305  }
306 
308  {
309  if ( !mGeometry->isMultipart() )
310  {
311  paintPolygon( p, mGeometry->asPolygon() );
312  }
313  else
314  {
315  QgsMultiPolygon m = mGeometry->asMultiPolygon();
316  for ( int i = 0; i < m.size(); i++ )
317  {
318  paintPolygon( p, m[i] );
319  }
320  }
321  break;
322  }
323 
326  return;
327  }
328  }
329  else if ( mFeature.hasGeometry() )
330  {
331  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
332  if ( !layer )
333  return;
334  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
335  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
336 
337  // Because lower level outlines must be covered by upper level fill color
338  // we render first with temporary opaque color, which is then replaced
339  // by final transparent fill color.
340  QColor tmpColor( 255, 0, 0, 255 );
341  QColor tmpFillColor( 0, 255, 0, 255 );
342 
343  QgsFeatureRenderer *renderer = getRenderer( context, tmpColor, tmpFillColor );
344  if ( layer && renderer )
345  {
346 
347  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
348  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
349  image.fill( 0 );
350  QPainter *imagePainter = new QPainter( &image );
351  imagePainter->setRenderHint( QPainter::Antialiasing, true );
352 
353  context.setPainter( imagePainter );
354 
355  renderer->startRender( context, layer->fields() );
356  renderer->renderFeature( mFeature, context );
357  renderer->stopRender( context );
358 
359  imagePainter->end();
360 
361  QColor color( mPen.color() ); // true output color
362  // coefficient to subtract alpha using green (temporary fill)
363  double k = ( 255. - mBrush.color().alpha() ) / 255.;
364  for ( int r = 0; r < image.height(); r++ )
365  {
366  for ( int c = 0; c < image.width(); c++ )
367  {
368  QRgb rgba = image.pixel( c, r );
369  int alpha = qAlpha( rgba );
370  if ( alpha > 0 )
371  {
372  int green = qGreen( rgba );
373  color.setAlpha( qBound<int>( 0, alpha - ( green * k ), 255 ) );
374 
375  image.setPixel( c, r, color.rgba() );
376  }
377  }
378  }
379 
380  p->drawImage( 0, 0, image );
381 
382  delete imagePainter;
383  delete renderer;
384  }
385  }
386 }
387 
389 {
390  if ( mGeometry )
391  {
392  QgsRectangle r = mGeometry->boundingBox();
393 
394  if ( r.isEmpty() )
395  {
396  double d = mMapCanvas->extent().width() * 0.005;
397  r.setXMinimum( r.xMinimum() - d );
398  r.setYMinimum( r.yMinimum() - d );
399  r.setXMaximum( r.xMaximum() + d );
400  r.setYMaximum( r.yMaximum() + d );
401  }
402 
403  setRect( r );
404  setVisible( mGeometry );
405  }
406  else if ( mFeature.hasGeometry() )
407  {
408  // We are currently using full map canvas extent for two reasons:
409  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
410  // bounding box
411  // 2) using different extent would result in shifted fill patterns
412 
413  // This is an hack to pass QgsMapCanvasItem::setRect what it
414  // expects (encoding of position and size of the item)
416  QgsPointXY topLeft = m2p.toMapPoint( 0, 0 );
417  double res = m2p.mapUnitsPerPixel();
418  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
419  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
420  setRect( rect );
421 
422  setVisible( true );
423  }
424  else
425  {
426  setRect( QgsRectangle() );
427  }
428 }
QgsPolygon asPolygon() const
Return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
QgsMultiPolyline asMultiPolyline() const
Return contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list.
A rectangle specified with double values.
Definition: qgsrectangle.h:38
Base class for all map layer types.
Definition: qgsmaplayer.h:54
QgsHighlight(QgsMapCanvas *mapCanvas, const QgsGeometry &geom, QgsMapLayer *layer)
Constructor for QgsHighlight.
virtual void updatePosition() override
called on changed extent or resize event to update position of the item
QgsRectangle rect() const
returns canvas item rectangle in map units
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry() )...
virtual QgsSymbol * subSymbol()
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:70
Simple marker symbol layer, consisting of a rendered shape with solid fill color and an stroke...
virtual void setWidth(double width)
double y
Definition: qgspointxy.h:47
A class to represent a 2D point.
Definition: qgspointxy.h:42
void setFillColor(const QColor &fillColor)
Set polygons fill color.
An abstract class for items that can be placed on the map canvas.
void setStrokeWidth(double w)
Sets the width of the marker&#39;s stroke.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:96
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:61
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:190
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
virtual double width() const
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void setStrokeWidth(double strokeWidth)
QgsPolyline asPolyline() const
Return contents of the geometry as a polyline if wkbType is WKBLineString, otherwise an empty list...
QVector< QgsPointXY > QgsPolyline
Polyline is represented as a vector of points.
Definition: qgsgeometry.h:48
double strokeWidth() const
Returns the width of the marker&#39;s stroke.
The QgsMapSettings class contains configuration for rendering of the map.
virtual void setColor(const QColor &color)
The fill color.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
QgsMultiPoint asMultiPoint() const
Return contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty list...
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)=0
Needs to be called when a new render cycle is started.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:35
QgsFields fields() const override
Returns the list of fields of this layer.
QgsUnitTypes::RenderUnit widthUnit() const
Returns the units for the line&#39;s width.
bool isEmpty() const
Returns true if the rectangle is empty.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:118
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:75
void updateRect()
recalculates needed rectangle
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
int symbolLayerCount()
Returns total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:139
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:76
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsFeatureRenderer * renderer()
Return renderer.
QVector< QgsPolyline > QgsPolygon
Polygon: first item of the list is outer ring, inner rings (if any) start from second item...
Definition: qgsgeometry.h:55
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:101
double mapUnitsPerPixel() const
Return current map units per pixel.
A store for object properties.
Definition: qgsproperty.h:189
QgsSymbolLayer * symbolLayer(int layer)
Returns a specific symbol layers contained in the symbol.
Definition: qgssymbol.cpp:320
const QgsMapToPixel & mapToPixel() const
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
double x
Definition: qgspointxy.h:46
virtual void setStrokeColor(const QColor &color)
Set stroke color.
A class to represent a vector.
Definition: qgsvector.h:26
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:106
QVector< QgsPolyline > QgsMultiPolyline
A collection of QgsPolylines that share a common collection of attributes.
Definition: qgsgeometry.h:69
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:91
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
virtual QgsSymbolList symbols(QgsRenderContext &context)
Returns list of symbols used by the renderer.
Definition: qgsrenderer.h:247
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit strokeWidthUnit() const
Returns the unit for the width of the marker&#39;s stroke.
Contains information about the context of a rendering operation.
QgsPointXY toMapPoint(double x, double y) const
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
virtual void stopRender(QgsRenderContext &context)=0
Needs to be called when a render cycle has finished to clean up.
virtual void setFillColor(const QColor &color)
Set fill color.
QgsPointXY asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
void setColor(const QColor &color)
Set line/stroke to color, polygon fill to color with alpha = 63.
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:80
QgsMultiPolygon asMultiPolygon() const
Return contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty ...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QgsMapCanvas * mMapCanvas
pointer to map canvas
QVector< QgsPointXY > QgsMultiPoint
A collection of QgsPoints that share a common collection of attributes.
Definition: qgsgeometry.h:62
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
virtual void paint(QPainter *p) override
function to be implemented by derived classes
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
Class for doing transforms between two map coordinate systems.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:96
int transform(const QgsCoordinateTransform &ct)
Transform this geometry as described by CoordinateTransform ct.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:101
const QgsMapLayer * layer() const
Definition: qgshighlight.h:86
QPointF toCanvasCoordinates(const QgsPointXY &point) const
transformation from map coordinates to screen coordinates
Represents a vector layer which manages a vector based data sets.
QSize outputSize() const
Return the size of the resulting map image.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:65
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false)
Render a feature using this renderer in the given context.
Definition: qgsrenderer.cpp:98
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:95
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
void setWidth(int width)
Set stroke width. Ignored in feature mode.