QGIS API Documentation  2.99.0-Master (585a4d3)
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 );
325 
326 
327  // Because lower level outlines must be covered by upper level fill color
328  // we render first with temporary opaque color, which is then replaced
329  // by final transparent fill color.
330  QColor tmpColor( 255, 0, 0, 255 );
331  QColor tmpFillColor( 0, 255, 0, 255 );
332 
333  std::unique_ptr< QgsFeatureRenderer > renderer = createRenderer( context, tmpColor, tmpFillColor );
334  if ( layer && renderer )
335  {
336 
337  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
338  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
339  image.fill( 0 );
340  QPainter imagePainter( &image );
341  imagePainter.setRenderHint( QPainter::Antialiasing, true );
342 
343  context.setPainter( &imagePainter );
344 
345  renderer->startRender( context, layer->fields() );
346  context.expressionContext().setFeature( mFeature );
347  renderer->renderFeature( mFeature, context );
348  renderer->stopRender( context );
349 
350  imagePainter.end();
351 
352  // true output color
353  int penRed = mPen.color().red();
354  int penGreen = mPen.color().green();
355  int penBlue = mPen.color().blue();
356  // coefficient to subtract alpha using green (temporary fill)
357  double k = ( 255. - mBrush.color().alpha() ) / 255.;
358  QRgb *line = nullptr;
359  for ( int r = 0; r < image.height(); r++ )
360  {
361  line = ( QRgb * )image.scanLine( r );
362  for ( int c = 0; c < image.width(); c++ )
363  {
364  int alpha = qAlpha( line[c] );
365  if ( alpha > 0 )
366  {
367  int green = qGreen( line[c] );
368  line[c] = qRgba( penRed, penGreen, penBlue, qBound<int>( 0, alpha - ( green * k ), 255 ) );
369  }
370  }
371  }
372 
373  p->drawImage( 0, 0, image );
374  }
375  }
376 }
377 
379 {
380  if ( mGeometry )
381  {
382  QgsRectangle r = mGeometry->boundingBox();
383 
384  if ( r.isEmpty() )
385  {
386  double d = mMapCanvas->extent().width() * 0.005;
387  r.setXMinimum( r.xMinimum() - d );
388  r.setYMinimum( r.yMinimum() - d );
389  r.setXMaximum( r.xMaximum() + d );
390  r.setYMaximum( r.yMaximum() + d );
391  }
392 
393  setRect( r );
394  setVisible( mGeometry );
395  }
396  else if ( mFeature.hasGeometry() )
397  {
398  // We are currently using full map canvas extent for two reasons:
399  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
400  // bounding box
401  // 2) using different extent would result in shifted fill patterns
402 
403  // This is an hack to pass QgsMapCanvasItem::setRect what it
404  // expects (encoding of position and size of the item)
406  QgsPointXY topLeft = m2p.toMapPoint( 0, 0 );
407  double res = m2p.mapUnitsPerPixel();
408  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
409  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
410  setRect( rect );
411 
412  setVisible( true );
413  }
414  else
415  {
416  setRect( QgsRectangle() );
417  }
418 }
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.
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:94
Simple marker symbol layer, consisting of a rendered shape with solid fill color and an stroke...
virtual void setWidth(double width)
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
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
~QgsHighlight() override
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:142
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:99
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:323
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:130
QgsExpressionContext & expressionContext()
Gets the expression context.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:115
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:104
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.
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:120
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:125
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...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
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:89
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.