QGIS API Documentation  3.6.0-Noosa (5873452)
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"
33 
34 /* Few notes about highlighting (RB):
35  - The highlight fill must always be partially transparent because above highlighted layer
36  may be another layer which must remain partially visible.
37  - Because single highlight color does not work well with layers using similar layer color
38  there were considered various possibilities but no optimal solution was found.
39  What does not work:
40  - lighter/darker color: it would work more or less for fully opaque highlight, but
41  overlaying transparent lighter color over original has small visual effect.
42  - complemetary color: mixing transparent (128) complement color with original color
43  results in grey for all colors
44  - contrast line style/ fill pattern: impression is not highligh but just different style
45  - line buffer with contrast (or 2 contrast) color: the same as with patterns, no highlight impression
46  - fill with highlight or contrast color but opaque and using pattern
47  (e.g. Qt::Dense7Pattern): again no highlight impression
48 */
49 
51  : QgsMapCanvasItem( mapCanvas )
52  , mLayer( layer )
53 {
54  mGeometry = !geom.isNull() ? new QgsGeometry( geom ) : nullptr;
55  init();
56 }
57 
59  : QgsMapCanvasItem( mapCanvas )
60  , mLayer( layer )
61  , mFeature( feature )
62 {
63  init();
64 }
65 
66 void QgsHighlight::init()
67 {
69  if ( ct.isValid() )
70  {
71  if ( mGeometry )
72  {
73  mGeometry->transform( ct );
74  }
75  else if ( mFeature.hasGeometry() )
76  {
77  QgsGeometry g = mFeature.geometry();
78  g.transform( ct );
79  mFeature.setGeometry( g );
80  }
81  }
82 
83  updateRect();
84  update();
85  setColor( QColor( Qt::lightGray ) );
86 }
87 
89 {
90  delete mGeometry;
91 }
92 
93 
94 void QgsHighlight::setColor( const QColor &color )
95 {
96  mColor = color;
97  mPen.setColor( color );
98  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
99  mBrush.setColor( fillColor );
100  mBrush.setStyle( Qt::SolidPattern );
101 }
102 
104 {
105  mFillColor = fillColor;
106  mBrush.setColor( fillColor );
107  mBrush.setStyle( Qt::SolidPattern );
108 }
109 
110 std::unique_ptr<QgsFeatureRenderer> QgsHighlight::createRenderer( QgsRenderContext &context, const QColor &color, const QColor &fillColor )
111 {
112  std::unique_ptr<QgsFeatureRenderer> renderer;
113  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
114  if ( layer && layer->renderer() )
115  {
116  renderer.reset( layer->renderer()->clone() );
117  }
118  if ( renderer )
119  {
120  Q_FOREACH ( QgsSymbol *symbol, renderer->symbols( context ) )
121  {
122  if ( !symbol ) continue;
123  setSymbol( symbol, context, color, fillColor );
124  }
125  }
126  return renderer;
127 }
128 
129 void QgsHighlight::setSymbol( QgsSymbol *symbol, const QgsRenderContext &context, const QColor &color, const QColor &fillColor )
130 {
131  if ( !symbol ) return;
132 
133 
134  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
135  {
136  QgsSymbolLayer *symbolLayer = symbol->symbolLayer( i );
137  if ( !symbolLayer ) continue;
138 
139  if ( symbolLayer->subSymbol() )
140  {
141  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
142  }
143  else
144  {
145  symbolLayer->setColor( color ); // line symbology layers
146  symbolLayer->setStrokeColor( color ); // marker and fill symbology layers
147  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
148 
149  // Data defined widths overwrite what we set here (widths do not work with data defined)
150  QgsSimpleMarkerSymbolLayer *simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayer *>( symbolLayer );
151  if ( simpleMarker )
152  {
153  simpleMarker->setStrokeWidth( getSymbolWidth( context, simpleMarker->strokeWidth(), simpleMarker->strokeWidthUnit() ) );
154  }
155  QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast<QgsSimpleLineSymbolLayer *>( symbolLayer );
156  if ( simpleLine )
157  {
158  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
159  }
160  QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast<QgsSimpleFillSymbolLayer *>( symbolLayer );
161  if ( simpleFill )
162  {
163  simpleFill->setStrokeWidth( getSymbolWidth( context, simpleFill->strokeWidth(), simpleFill->outputUnit() ) );
164  }
167  }
168  }
169 }
170 
171 double QgsHighlight::getSymbolWidth( const QgsRenderContext &context, double width, QgsUnitTypes::RenderUnit unit )
172 {
173  // if necessary scale mm to map units
174  double scale = 1.;
175  if ( unit == QgsUnitTypes::RenderMapUnits )
176  {
178  }
179  width = std::max( width + 2 * mBuffer * scale, mMinWidth * scale );
180  return width;
181 }
182 
184 {
185  mWidth = width;
186  mPen.setWidth( width );
187 }
188 
189 void QgsHighlight::paintPoint( QPainter *p, const QgsPointXY &point )
190 {
191  QPolygonF r( 5 );
192 
193  double d = mMapCanvas->extent().width() * 0.005;
194  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
195  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
196  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
197  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
198  r[4] = r[0];
199 
200  p->drawPolygon( r );
201 }
202 
203 void QgsHighlight::paintLine( QPainter *p, QgsPolylineXY line )
204 {
205  QPolygonF polygon( line.size() );
206 
207  for ( int i = 0; i < line.size(); i++ )
208  {
209  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
210  }
211 
212  p->drawPolyline( polygon );
213 }
214 
215 void QgsHighlight::paintPolygon( QPainter *p, const QgsPolygonXY &polygon )
216 {
217  // OddEven fill rule by default
218  QPainterPath path;
219 
220  p->setPen( mPen );
221  p->setBrush( mBrush );
222 
223  for ( const auto &sourceRing : polygon )
224  {
225  if ( sourceRing.empty() )
226  continue;
227 
228  QPolygonF ring;
229  ring.reserve( sourceRing.size() + 1 );
230 
231  QPointF lastVertex;
232  for ( const auto &sourceVertex : sourceRing )
233  {
234  //adding point only if it is more than a pixel apart from the previous one
235  const QPointF curVertex = toCanvasCoordinates( sourceVertex ) - pos();
236  if ( ring.isEmpty() || std::abs( ring.back().x() - curVertex.x() ) > 1 || std::abs( ring.back().y() - curVertex.y() ) > 1 )
237  {
238  ring.push_back( curVertex );
239  }
240  lastVertex = curVertex;
241  }
242 
243  ring.push_back( ring.at( 0 ) );
244 
245  path.addPolygon( ring );
246  }
247 
248  p->drawPath( path );
249 }
250 
252 {
253  if ( isVisible() ) updateRect();
254 }
255 
256 void QgsHighlight::paint( QPainter *p )
257 {
258  if ( mGeometry )
259  {
260  p->setPen( mPen );
261  p->setBrush( mBrush );
262 
263  switch ( mGeometry->type() )
264  {
266  {
267  if ( !mGeometry->isMultipart() )
268  {
269  paintPoint( p, mGeometry->asPoint() );
270  }
271  else
272  {
273  QgsMultiPointXY m = mGeometry->asMultiPoint();
274  for ( int i = 0; i < m.size(); i++ )
275  {
276  paintPoint( p, m[i] );
277  }
278  }
279  }
280  break;
281 
283  {
284  if ( !mGeometry->isMultipart() )
285  {
286  paintLine( p, mGeometry->asPolyline() );
287  }
288  else
289  {
290  QgsMultiPolylineXY m = mGeometry->asMultiPolyline();
291 
292  for ( int i = 0; i < m.size(); i++ )
293  {
294  paintLine( p, m[i] );
295  }
296  }
297  break;
298  }
299 
301  {
302  if ( !mGeometry->isMultipart() )
303  {
304  paintPolygon( p, mGeometry->asPolygon() );
305  }
306  else
307  {
308  QgsMultiPolygonXY m = mGeometry->asMultiPolygon();
309  for ( int i = 0; i < m.size(); i++ )
310  {
311  paintPolygon( p, m[i] );
312  }
313  }
314  break;
315  }
316 
319  return;
320  }
321  }
322  else if ( mFeature.hasGeometry() )
323  {
324  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
325  if ( !layer )
326  return;
327  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
328  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
330 
331 
332  // Because lower level outlines must be covered by upper level fill color
333  // we render first with temporary opaque color, which is then replaced
334  // by final transparent fill color.
335  QColor tmpColor( 255, 0, 0, 255 );
336  QColor tmpFillColor( 0, 255, 0, 255 );
337 
338  std::unique_ptr< QgsFeatureRenderer > renderer = createRenderer( context, tmpColor, tmpFillColor );
339  if ( layer && renderer )
340  {
341 
342  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
343  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
344  image.fill( 0 );
345  QPainter imagePainter( &image );
346  imagePainter.setRenderHint( QPainter::Antialiasing, true );
347 
348  context.setPainter( &imagePainter );
349 
350  renderer->startRender( context, layer->fields() );
351  context.expressionContext().setFeature( mFeature );
352  renderer->renderFeature( mFeature, context );
353  renderer->stopRender( context );
354 
355  imagePainter.end();
356 
357  // true output color
358  int penRed = mPen.color().red();
359  int penGreen = mPen.color().green();
360  int penBlue = mPen.color().blue();
361  // coefficient to subtract alpha using green (temporary fill)
362  double k = ( 255. - mBrush.color().alpha() ) / 255.;
363  QRgb *line = nullptr;
364  for ( int r = 0; r < image.height(); r++ )
365  {
366  line = reinterpret_cast<QRgb *>( image.scanLine( r ) );
367  for ( int c = 0; c < image.width(); c++ )
368  {
369  int alpha = qAlpha( line[c] );
370  if ( alpha > 0 )
371  {
372  int green = qGreen( line[c] );
373  line[c] = qRgba( penRed, penGreen, penBlue, qBound<int>( 0, alpha - ( green * k ), 255 ) );
374  }
375  }
376  }
377 
378  p->drawImage( 0, 0, image );
379  }
380  }
381 }
382 
384 {
385  if ( mGeometry )
386  {
387  QgsRectangle r = mGeometry->boundingBox();
388 
389  if ( r.isEmpty() )
390  {
391  double d = mMapCanvas->extent().width() * 0.005;
392  r.setXMinimum( r.xMinimum() - d );
393  r.setYMinimum( r.yMinimum() - d );
394  r.setXMaximum( r.xMaximum() + d );
395  r.setYMaximum( r.yMaximum() + d );
396  }
397 
398  setRect( r );
399  setVisible( mGeometry );
400  }
401  else if ( mFeature.hasGeometry() )
402  {
403  // We are currently using full map canvas extent for two reasons:
404  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
405  // bounding box
406  // 2) using different extent would result in shifted fill patterns
407 
408  // This is an hack to pass QgsMapCanvasItem::setRect what it
409  // expects (encoding of position and size of the item)
411  QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
412  double res = m2p.mapUnitsPerPixel();
413  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
414  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
415  setRect( rect );
416 
417  setVisible( true );
418  }
419  else
420  {
421  setRect( QgsRectangle() );
422  }
423 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:64
QgsMapLayer * layer() const
Returns the layer for which this highlight has been created.
Definition: qgshighlight.h:139
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.
QColor color() const
Returns the line/stroke color.
Definition: qgshighlight.h:82
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
virtual QgsSymbol * subSymbol()
Returns the symbol&#39;s sub symbol, if present.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:135
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:68
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:106
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:150
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition: qgsgeometry.h:74
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
~QgsHighlight() override
virtual double width() const
Returns the estimated width for the line symbol layer.
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:85
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:78
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer&#39;s CRS to destination CRS.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:37
QgsFields fields() const FINAL
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.
Definition: qgsrectangle.h:426
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:140
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.
QColor fillColor() const
Returns the fill color.
Definition: qgshighlight.h:93
QgsFeatureRenderer * renderer()
Returns renderer.
double mapUnitsPerPixel() const
Returns current map units per pixel.
A store for object properties.
Definition: qgsproperty.h:229
QgsSymbolLayer * symbolLayer(int layer)
Returns a specific symbol layer contained in the symbol.
Definition: qgssymbol.cpp:342
const QgsMapToPixel & mapToPixel() const
double x
Definition: qgspointxy.h:47
virtual void setStrokeColor(const QColor &color)
Set stroke color.
A class to represent a vector.
Definition: qgsvector.h:29
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
QgsExpressionContext & expressionContext()
Gets the expression context.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
const QgsMapSettings & mapSettings() const
Gets 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:44
Contains information about the context of a rendering operation.
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.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
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:145
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:167
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
QgsMultiPointXY asMultiPoint() const
Returns the contents of the geometry as a multi-point.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:110
QgsGeometry geometry
Definition: qgsfeature.h:67
int width() const
Returns the stroke width.
Definition: qgshighlight.h:107
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 the contents of the geometry as a multi-polygon.
QSize outputSize() const
Returns the size of the resulting map image.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:130
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:111
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
void setWidth(int width)
Set stroke width.