QGIS API Documentation  2.99.0-Master (19b062c)
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 {
53  mGeometry = !geom.isNull() ? new QgsGeometry( geom ) : nullptr;
54  init();
55 }
56 
58  : QgsMapCanvasItem( mapCanvas )
59  , mLayer( layer )
60  , mFeature( feature )
61 {
62  init();
63 }
64 
65 void QgsHighlight::init()
66 {
68  if ( ct.isValid() )
69  {
70  if ( mGeometry )
71  {
72  mGeometry->transform( ct );
73  }
74  else if ( mFeature.hasGeometry() )
75  {
76  QgsGeometry g = mFeature.geometry();
77  g.transform( ct );
78  mFeature.setGeometry( g );
79  }
80  }
81 
82  updateRect();
83  update();
84  setColor( QColor( Qt::lightGray ) );
85 }
86 
88 {
89  delete mGeometry;
90 }
91 
92 void QgsHighlight::setColor( const QColor &color )
93 {
94  mPen.setColor( color );
95  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
96  mBrush.setColor( fillColor );
97  mBrush.setStyle( Qt::SolidPattern );
98 }
99 
100 void QgsHighlight::setFillColor( const QColor &fillColor )
101 {
102  mBrush.setColor( fillColor );
103  mBrush.setStyle( Qt::SolidPattern );
104 }
105 
106 std::unique_ptr<QgsFeatureRenderer> QgsHighlight::createRenderer( QgsRenderContext &context, const QColor &color, const QColor &fillColor )
107 {
108  std::unique_ptr<QgsFeatureRenderer> renderer;
109  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
110  if ( layer && layer->renderer() )
111  {
112  renderer.reset( layer->renderer()->clone() );
113  }
114  if ( renderer )
115  {
116  Q_FOREACH ( QgsSymbol *symbol, renderer->symbols( context ) )
117  {
118  if ( !symbol ) continue;
119  setSymbol( symbol, context, color, fillColor );
120  }
121  }
122  return renderer;
123 }
124 
125 void QgsHighlight::setSymbol( QgsSymbol *symbol, const QgsRenderContext &context, const QColor &color, const QColor &fillColor )
126 {
127  if ( !symbol ) return;
128 
129 
130  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
131  {
132  QgsSymbolLayer *symbolLayer = symbol->symbolLayer( i );
133  if ( !symbolLayer ) continue;
134 
135  if ( symbolLayer->subSymbol() )
136  {
137  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
138  }
139  else
140  {
141  symbolLayer->setColor( color ); // line symbology layers
142  symbolLayer->setStrokeColor( color ); // marker and fill symbology layers
143  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
144 
145  // Data defined widths overwrite what we set here (widths do not work with data defined)
146  QgsSimpleMarkerSymbolLayer *simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayer *>( symbolLayer );
147  if ( simpleMarker )
148  {
149  simpleMarker->setStrokeWidth( getSymbolWidth( context, simpleMarker->strokeWidth(), simpleMarker->strokeWidthUnit() ) );
150  }
151  QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast<QgsSimpleLineSymbolLayer *>( symbolLayer );
152  if ( simpleLine )
153  {
154  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
155  }
156  QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast<QgsSimpleFillSymbolLayer *>( symbolLayer );
157  if ( simpleFill )
158  {
159  simpleFill->setStrokeWidth( getSymbolWidth( context, simpleFill->strokeWidth(), simpleFill->outputUnit() ) );
160  }
163  }
164  }
165 }
166 
167 double QgsHighlight::getSymbolWidth( const QgsRenderContext &context, double width, QgsUnitTypes::RenderUnit unit )
168 {
169  // if necessary scale mm to map units
170  double scale = 1.;
171  if ( unit == QgsUnitTypes::RenderMapUnits )
172  {
174  }
175  width = std::max( width + 2 * mBuffer * scale, mMinWidth * scale );
176  return width;
177 }
178 
179 void QgsHighlight::setWidth( int width )
180 {
181  mPen.setWidth( width );
182 }
183 
184 void QgsHighlight::paintPoint( QPainter *p, const QgsPointXY &point )
185 {
186  QPolygonF r( 5 );
187 
188  double d = mMapCanvas->extent().width() * 0.005;
189  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
190  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
191  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
192  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
193  r[4] = r[0];
194 
195  p->drawPolygon( r );
196 }
197 
198 void QgsHighlight::paintLine( QPainter *p, QgsPolylineXY line )
199 {
200  QPolygonF polygon( line.size() );
201 
202  for ( int i = 0; i < line.size(); i++ )
203  {
204  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
205  }
206 
207  p->drawPolyline( polygon );
208 }
209 
210 void QgsHighlight::paintPolygon( QPainter *p, const QgsPolygonXY &polygon )
211 {
212  // OddEven fill rule by default
213  QPainterPath path;
214 
215  p->setPen( mPen );
216  p->setBrush( mBrush );
217 
218  for ( const auto &sourceRing : polygon )
219  {
220  if ( sourceRing.empty() )
221  continue;
222 
223  QPolygonF ring;
224  ring.reserve( sourceRing.size() + 1 );
225 
226  QPointF lastVertex;
227  for ( const auto &sourceVertex : sourceRing )
228  {
229  //adding point only if it is more than a pixel apart from the previous one
230  const QPointF curVertex = toCanvasCoordinates( sourceVertex ) - pos();
231  if ( ring.isEmpty() || std::abs( ring.back().x() - curVertex.x() ) > 1 || std::abs( ring.back().y() - curVertex.y() ) > 1 )
232  {
233  ring.push_back( curVertex );
234  }
235  lastVertex = curVertex;
236  }
237 
238  ring.push_back( ring.at( 0 ) );
239 
240  path.addPolygon( ring );
241  }
242 
243  p->drawPath( path );
244 }
245 
247 {
248  if ( isVisible() ) updateRect();
249 }
250 
251 void QgsHighlight::paint( QPainter *p )
252 {
253  if ( mGeometry )
254  {
255  p->setPen( mPen );
256  p->setBrush( mBrush );
257 
258  switch ( mGeometry->type() )
259  {
261  {
262  if ( !mGeometry->isMultipart() )
263  {
264  paintPoint( p, mGeometry->asPoint() );
265  }
266  else
267  {
268  QgsMultiPointXY m = mGeometry->asMultiPoint();
269  for ( int i = 0; i < m.size(); i++ )
270  {
271  paintPoint( p, m[i] );
272  }
273  }
274  }
275  break;
276 
278  {
279  if ( !mGeometry->isMultipart() )
280  {
281  paintLine( p, mGeometry->asPolyline() );
282  }
283  else
284  {
285  QgsMultiPolylineXY m = mGeometry->asMultiPolyline();
286 
287  for ( int i = 0; i < m.size(); i++ )
288  {
289  paintLine( p, m[i] );
290  }
291  }
292  break;
293  }
294 
296  {
297  if ( !mGeometry->isMultipart() )
298  {
299  paintPolygon( p, mGeometry->asPolygon() );
300  }
301  else
302  {
303  QgsMultiPolygonXY m = mGeometry->asMultiPolygon();
304  for ( int i = 0; i < m.size(); i++ )
305  {
306  paintPolygon( p, m[i] );
307  }
308  }
309  break;
310  }
311 
314  return;
315  }
316  }
317  else if ( mFeature.hasGeometry() )
318  {
319  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
320  if ( !layer )
321  return;
322  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
323  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
324 
325  // Because lower level outlines must be covered by upper level fill color
326  // we render first with temporary opaque color, which is then replaced
327  // by final transparent fill color.
328  QColor tmpColor( 255, 0, 0, 255 );
329  QColor tmpFillColor( 0, 255, 0, 255 );
330 
331  std::unique_ptr< QgsFeatureRenderer > renderer = createRenderer( context, tmpColor, tmpFillColor );
332  if ( layer && renderer )
333  {
334 
335  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
336  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
337  image.fill( 0 );
338  QPainter imagePainter( &image );
339  imagePainter.setRenderHint( QPainter::Antialiasing, true );
340 
341  context.setPainter( &imagePainter );
342 
343  renderer->startRender( context, layer->fields() );
344  renderer->renderFeature( mFeature, context );
345  renderer->stopRender( context );
346 
347  imagePainter.end();
348 
349  QColor color( mPen.color() ); // true output color
350  // coefficient to subtract alpha using green (temporary fill)
351  double k = ( 255. - mBrush.color().alpha() ) / 255.;
352  for ( int r = 0; r < image.height(); r++ )
353  {
354  for ( int c = 0; c < image.width(); c++ )
355  {
356  QRgb rgba = image.pixel( c, r );
357  int alpha = qAlpha( rgba );
358  if ( alpha > 0 )
359  {
360  int green = qGreen( rgba );
361  color.setAlpha( qBound<int>( 0, alpha - ( green * k ), 255 ) );
362 
363  image.setPixel( c, r, color.rgba() );
364  }
365  }
366  }
367 
368  p->drawImage( 0, 0, image );
369  }
370  }
371 }
372 
374 {
375  if ( mGeometry )
376  {
377  QgsRectangle r = mGeometry->boundingBox();
378 
379  if ( r.isEmpty() )
380  {
381  double d = mMapCanvas->extent().width() * 0.005;
382  r.setXMinimum( r.xMinimum() - d );
383  r.setYMinimum( r.yMinimum() - d );
384  r.setXMaximum( r.xMaximum() + d );
385  r.setYMaximum( r.yMaximum() + d );
386  }
387 
388  setRect( r );
389  setVisible( mGeometry );
390  }
391  else if ( mFeature.hasGeometry() )
392  {
393  // We are currently using full map canvas extent for two reasons:
394  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
395  // bounding box
396  // 2) using different extent would result in shifted fill patterns
397 
398  // This is an hack to pass QgsMapCanvasItem::setRect what it
399  // expects (encoding of position and size of the item)
401  QgsPointXY topLeft = m2p.toMapPoint( 0, 0 );
402  double res = m2p.mapUnitsPerPixel();
403  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
404  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
405  setRect( rect );
406 
407  setVisible( true );
408  }
409  else
410  {
411  setRect( QgsRectangle() );
412  }
413 }
A rectangle specified with double values.
Definition: qgsrectangle.h:39
Base class for all map layer types.
Definition: qgsmaplayer.h:56
QgsMapLayer * layer() const
Return the layer for which this highlight has been created.
Definition: qgshighlight.h:108
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:90
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:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
void setFillColor(const QColor &fillColor)
Fill color for the highlight.
QVector< QgsPolylineXY > QgsPolygonXY
Polygon: first item of the list is outer ring, inner rings (if any) start from second item...
Definition: qgsgeometry.h:73
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:111
int symbolLayerCount() const
Returns total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:143
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition: qgsgeometry.h:79
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
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:74
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...
QVector< QgsPolygonXY > QgsMultiPolygonXY
A collection of QgsPolygons that share a common collection of attributes.
Definition: qgsgeometry.h:90
void setStrokeWidth(double strokeWidth)
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.
QVector< QgsPolylineXY > QgsMultiPolylineXY
A collection of QgsPolylines that share a common collection of attributes.
Definition: qgsgeometry.h:83
QgsMultiPolylineXY asMultiPolyline() const
Returns contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:36
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.
QgsPolygonXY asPolygon() const
Returns contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:138
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:95
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
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsFeatureRenderer * renderer()
Return renderer.
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:229
QgsSymbolLayer * symbolLayer(int layer)
Returns a specific symbol layers contained in the symbol.
Definition: qgssymbol.cpp:319
const QgsMapToPixel & mapToPixel() const
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
double x
Definition: qgspointxy.h:47
virtual void setStrokeColor(const QColor &color)
Set stroke color.
A class to represent a vector.
Definition: qgsvector.h:27
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:126
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:111
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
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.
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:49
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 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:100
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QgsMapCanvas * mMapCanvas
pointer to map canvas
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:116
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:121
QgsPolylineXY asPolyline() const
Returns contents of the geometry as a polyline if wkbType is WKBLineString, otherwise an empty list...
QgsMultiPointXY asMultiPoint() const
Returns contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty lis...
QPointF toCanvasCoordinates(const QgsPointXY &point) const
transformation from map coordinates to screen coordinates
Represents a vector layer which manages a vector based data sets.
QgsMultiPolygonXY asMultiPolygon() const
Returns contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty list.
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:85
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:100
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
void setWidth(int width)
Set stroke width.