QGIS API Documentation  2.99.0-Master (dc72e14)
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  , mLayer( static_cast<QgsMapLayer *>( layer ) )
72  , mFeature( feature )
73  , mBuffer( 0 )
74  , mMinWidth( 0 )
75 {
76  init();
77 }
78 
79 void QgsHighlight::init()
80 {
82  if ( ct.isValid() )
83  {
84  if ( mGeometry )
85  {
86  mGeometry->transform( ct );
87  }
88  else if ( mFeature.hasGeometry() )
89  {
90  QgsGeometry g = mFeature.geometry();
91  g.transform( ct );
92  mFeature.setGeometry( g );
93  }
94  }
95 
96  updateRect();
97  update();
98  setColor( QColor( Qt::lightGray ) );
99 }
100 
102 {
103  delete mGeometry;
104 }
105 
106 void QgsHighlight::setColor( const QColor &color )
107 {
108  mPen.setColor( color );
109  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
110  mBrush.setColor( fillColor );
111  mBrush.setStyle( Qt::SolidPattern );
112 }
113 
114 void QgsHighlight::setFillColor( const QColor &fillColor )
115 {
116  mBrush.setColor( fillColor );
117  mBrush.setStyle( Qt::SolidPattern );
118 }
119 
120 QgsFeatureRenderer *QgsHighlight::getRenderer( QgsRenderContext &context, const QColor &color, const QColor &fillColor )
121 {
122  QgsFeatureRenderer *renderer = nullptr;
123  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
124  if ( layer && layer->renderer() )
125  {
126  renderer = layer->renderer()->clone();
127  }
128  if ( renderer )
129  {
130  Q_FOREACH ( QgsSymbol *symbol, renderer->symbols( context ) )
131  {
132  if ( !symbol ) continue;
133  setSymbol( symbol, context, color, fillColor );
134  }
135  }
136  return renderer;
137 }
138 
139 void QgsHighlight::setSymbol( QgsSymbol *symbol, const QgsRenderContext &context, const QColor &color, const QColor &fillColor )
140 {
141  if ( !symbol ) return;
142 
143 
144  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
145  {
146  QgsSymbolLayer *symbolLayer = symbol->symbolLayer( i );
147  if ( !symbolLayer ) continue;
148 
149  if ( symbolLayer->subSymbol() )
150  {
151  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
152  }
153  else
154  {
155  symbolLayer->setColor( color ); // line symbology layers
156  symbolLayer->setStrokeColor( color ); // marker and fill symbology layers
157  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
158 
159  // Data defined widths overwrite what we set here (widths do not work with data defined)
160  QgsSimpleMarkerSymbolLayer *simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayer *>( symbolLayer );
161  if ( simpleMarker )
162  {
163  simpleMarker->setStrokeWidth( getSymbolWidth( context, simpleMarker->strokeWidth(), simpleMarker->strokeWidthUnit() ) );
164  }
165  QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast<QgsSimpleLineSymbolLayer *>( symbolLayer );
166  if ( simpleLine )
167  {
168  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
169  }
170  QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast<QgsSimpleFillSymbolLayer *>( symbolLayer );
171  if ( simpleFill )
172  {
173  simpleFill->setStrokeWidth( getSymbolWidth( context, simpleFill->strokeWidth(), simpleFill->outputUnit() ) );
174  }
177  }
178  }
179 }
180 
181 double QgsHighlight::getSymbolWidth( const QgsRenderContext &context, double width, QgsUnitTypes::RenderUnit unit )
182 {
183  // if necessary scale mm to map units
184  double scale = 1.;
185  if ( unit == QgsUnitTypes::RenderMapUnits )
186  {
188  }
189  width = std::max( width + 2 * mBuffer * scale, mMinWidth * scale );
190  return width;
191 }
192 
193 void QgsHighlight::setWidth( int width )
194 {
195  mPen.setWidth( width );
196 }
197 
198 void QgsHighlight::paintPoint( QPainter *p, const QgsPointXY &point )
199 {
200  QPolygonF r( 5 );
201 
202  double d = mMapCanvas->extent().width() * 0.005;
203  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
204  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
205  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
206  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
207  r[4] = r[0];
208 
209  p->drawPolygon( r );
210 }
211 
212 void QgsHighlight::paintLine( QPainter *p, QgsPolyline line )
213 {
214  QPolygonF polygon( line.size() );
215 
216  for ( int i = 0; i < line.size(); i++ )
217  {
218  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
219  }
220 
221  p->drawPolyline( polygon );
222 }
223 
224 void QgsHighlight::paintPolygon( QPainter *p, QgsPolygon polygon )
225 {
226  // OddEven fill rule by default
227  QPainterPath path;
228 
229  p->setPen( mPen );
230  p->setBrush( mBrush );
231 
232  for ( int i = 0; i < polygon.size(); i++ )
233  {
234  if ( polygon[i].empty() ) continue;
235 
236  QPolygonF ring;
237  ring.reserve( polygon[i].size() + 1 );
238 
239  for ( int j = 0; j < polygon[i].size(); j++ )
240  {
241  //adding point only if it is more than a pixel apart from the previous one
242  const QPointF cur = toCanvasCoordinates( polygon[i][j] ) - pos();
243  if ( 0 == j || std::abs( ring.back().x() - cur.x() ) > 1 || std::abs( ring.back().y() - cur.y() ) > 1 )
244  {
245  ring.push_back( cur );
246  }
247  }
248 
249  ring.push_back( ring[ 0 ] );
250 
251  path.addPolygon( ring );
252  }
253 
254  p->drawPath( path );
255 }
256 
258 {
259  updateRect();
260 }
261 
262 void QgsHighlight::paint( QPainter *p )
263 {
264  if ( mGeometry )
265  {
266  p->setPen( mPen );
267  p->setBrush( mBrush );
268 
269  switch ( mGeometry->type() )
270  {
272  {
273  if ( !mGeometry->isMultipart() )
274  {
275  paintPoint( p, mGeometry->asPoint() );
276  }
277  else
278  {
279  QgsMultiPoint m = mGeometry->asMultiPoint();
280  for ( int i = 0; i < m.size(); i++ )
281  {
282  paintPoint( p, m[i] );
283  }
284  }
285  }
286  break;
287 
289  {
290  if ( !mGeometry->isMultipart() )
291  {
292  paintLine( p, mGeometry->asPolyline() );
293  }
294  else
295  {
296  QgsMultiPolyline m = mGeometry->asMultiPolyline();
297 
298  for ( int i = 0; i < m.size(); i++ )
299  {
300  paintLine( p, m[i] );
301  }
302  }
303  break;
304  }
305 
307  {
308  if ( !mGeometry->isMultipart() )
309  {
310  paintPolygon( p, mGeometry->asPolygon() );
311  }
312  else
313  {
314  QgsMultiPolygon m = mGeometry->asMultiPolygon();
315  for ( int i = 0; i < m.size(); i++ )
316  {
317  paintPolygon( p, m[i] );
318  }
319  }
320  break;
321  }
322 
325  return;
326  }
327  }
328  else if ( mFeature.hasGeometry() )
329  {
330  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
331  if ( !layer )
332  return;
333  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
334  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
335 
336  // Because lower level outlines must be covered by upper level fill color
337  // we render first with temporary opaque color, which is then replaced
338  // by final transparent fill color.
339  QColor tmpColor( 255, 0, 0, 255 );
340  QColor tmpFillColor( 0, 255, 0, 255 );
341 
342  QgsFeatureRenderer *renderer = getRenderer( context, tmpColor, tmpFillColor );
343  if ( layer && renderer )
344  {
345 
346  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
347  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
348  image.fill( 0 );
349  QPainter *imagePainter = new QPainter( &image );
350  imagePainter->setRenderHint( QPainter::Antialiasing, true );
351 
352  context.setPainter( imagePainter );
353 
354  renderer->startRender( context, layer->fields() );
355  renderer->renderFeature( mFeature, context );
356  renderer->stopRender( context );
357 
358  imagePainter->end();
359 
360  QColor color( mPen.color() ); // true output color
361  // coefficient to subtract alpha using green (temporary fill)
362  double k = ( 255. - mBrush.color().alpha() ) / 255.;
363  for ( int r = 0; r < image.height(); r++ )
364  {
365  for ( int c = 0; c < image.width(); c++ )
366  {
367  QRgb rgba = image.pixel( c, r );
368  int alpha = qAlpha( rgba );
369  if ( alpha > 0 )
370  {
371  int green = qGreen( rgba );
372  color.setAlpha( qBound<int>( 0, alpha - ( green * k ), 255 ) );
373 
374  image.setPixel( c, r, color.rgba() );
375  }
376  }
377  }
378 
379  p->drawImage( 0, 0, image );
380 
381  delete imagePainter;
382  delete renderer;
383  }
384  }
385 }
386 
388 {
389  if ( mGeometry )
390  {
391  QgsRectangle r = mGeometry->boundingBox();
392 
393  if ( r.isEmpty() )
394  {
395  double d = mMapCanvas->extent().width() * 0.005;
396  r.setXMinimum( r.xMinimum() - d );
397  r.setYMinimum( r.yMinimum() - d );
398  r.setXMaximum( r.xMaximum() + d );
399  r.setYMaximum( r.yMaximum() + d );
400  }
401 
402  setRect( r );
403  setVisible( mGeometry );
404  }
405  else if ( mFeature.hasGeometry() )
406  {
407  // We are currently using full map canvas extent for two reasons:
408  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
409  // bounding box
410  // 2) using different extent would result in shifted fill patterns
411 
412  // This is an hack to pass QgsMapCanvasItem::setRect what it
413  // expects (encoding of position and size of the item)
415  QgsPointXY topLeft = m2p.toMapPoint( 0, 0 );
416  double res = m2p.mapUnitsPerPixel();
417  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
418  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
419  setRect( rect );
420 
421  setVisible( true );
422  }
423  else
424  {
425  setRect( QgsRectangle() );
426  }
427 }
QgsPolygon asPolygon() const
Returns contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
QgsMultiPolyline asMultiPolyline() const
Returns 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()
Returns the symbol&#39;s sub symbol, if present.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:75
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:92
int symbolLayerCount() const
Returns total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:139
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
Returns 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
Returns contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty lis...
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:123
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:80
void updateRect()
recalculates needed rectangle
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
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:72
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:111
QVector< QgsPolyline > QgsMultiPolyline
A collection of QgsPolylines that share a common collection of attributes.
Definition: qgsgeometry.h:65
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:96
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.
OperationResult transform(const QgsCoordinateTransform &ct)
Transforms this geometry as described by CoordinateTransform ct.
QgsPointXY asPoint() const
Returns 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:85
QgsMultiPolygon asMultiPolygon() const
Returns contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty list.
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:61
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:101
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:106
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:70
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:97
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:98
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.