QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
93 void QgsHighlight::setColor( const QColor &color )
94 {
95  mColor = color;
96  mPen.setColor( color );
97  QColor fillColor( color.red(), color.green(), color.blue(), 63 );
98  mBrush.setColor( fillColor );
99  mBrush.setStyle( Qt::SolidPattern );
100 }
101 
103 {
104  mFillColor = fillColor;
105  mBrush.setColor( fillColor );
106  mBrush.setStyle( Qt::SolidPattern );
107 }
108 
109 std::unique_ptr<QgsFeatureRenderer> QgsHighlight::createRenderer( QgsRenderContext &context, const QColor &color, const QColor &fillColor )
110 {
111  std::unique_ptr<QgsFeatureRenderer> renderer;
112  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
113  if ( layer && layer->renderer() )
114  {
115  renderer.reset( layer->renderer()->clone() );
116  }
117  if ( renderer )
118  {
119  Q_FOREACH ( QgsSymbol *symbol, renderer->symbols( context ) )
120  {
121  if ( !symbol ) continue;
122  setSymbol( symbol, context, color, fillColor );
123  }
124  }
125  return renderer;
126 }
127 
128 void QgsHighlight::setSymbol( QgsSymbol *symbol, const QgsRenderContext &context, const QColor &color, const QColor &fillColor )
129 {
130  if ( !symbol ) return;
131 
132 
133  for ( int i = symbol->symbolLayerCount() - 1; i >= 0; i-- )
134  {
135  QgsSymbolLayer *symbolLayer = symbol->symbolLayer( i );
136  if ( !symbolLayer ) continue;
137 
138  if ( symbolLayer->subSymbol() )
139  {
140  setSymbol( symbolLayer->subSymbol(), context, color, fillColor );
141  }
142  else
143  {
144  symbolLayer->setColor( color ); // line symbology layers
145  symbolLayer->setStrokeColor( color ); // marker and fill symbology layers
146  symbolLayer->setFillColor( fillColor ); // marker and fill symbology layers
147 
148  // Data defined widths overwrite what we set here (widths do not work with data defined)
149  QgsSimpleMarkerSymbolLayer *simpleMarker = dynamic_cast<QgsSimpleMarkerSymbolLayer *>( symbolLayer );
150  if ( simpleMarker )
151  {
152  simpleMarker->setStrokeWidth( getSymbolWidth( context, simpleMarker->strokeWidth(), simpleMarker->strokeWidthUnit() ) );
153  }
154  QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast<QgsSimpleLineSymbolLayer *>( symbolLayer );
155  if ( simpleLine )
156  {
157  simpleLine->setWidth( getSymbolWidth( context, simpleLine->width(), simpleLine->widthUnit() ) );
158  }
159  QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast<QgsSimpleFillSymbolLayer *>( symbolLayer );
160  if ( simpleFill )
161  {
162  simpleFill->setStrokeWidth( getSymbolWidth( context, simpleFill->strokeWidth(), simpleFill->outputUnit() ) );
163  }
166  }
167  }
168 }
169 
170 double QgsHighlight::getSymbolWidth( const QgsRenderContext &context, double width, QgsUnitTypes::RenderUnit unit )
171 {
172  // if necessary scale mm to map units
173  double scale = 1.;
174  if ( unit == QgsUnitTypes::RenderMapUnits )
175  {
177  }
178  width = std::max( width + 2 * mBuffer * scale, mMinWidth * scale );
179  return width;
180 }
181 
183 {
184  mWidth = width;
185  mPen.setWidth( width );
186 }
187 
188 void QgsHighlight::paintPoint( QPainter *p, const QgsPointXY &point )
189 {
190  QPolygonF r( 5 );
191 
192  double d = mMapCanvas->extent().width() * 0.005;
193  r[0] = toCanvasCoordinates( point + QgsVector( -d, -d ) ) - pos();
194  r[1] = toCanvasCoordinates( point + QgsVector( d, -d ) ) - pos();
195  r[2] = toCanvasCoordinates( point + QgsVector( d, d ) ) - pos();
196  r[3] = toCanvasCoordinates( point + QgsVector( -d, d ) ) - pos();
197  r[4] = r[0];
198 
199  p->drawPolygon( r );
200 }
201 
202 void QgsHighlight::paintLine( QPainter *p, QgsPolylineXY line )
203 {
204  QPolygonF polygon( line.size() );
205 
206  for ( int i = 0; i < line.size(); i++ )
207  {
208  polygon[i] = toCanvasCoordinates( line[i] ) - pos();
209  }
210 
211  p->drawPolyline( polygon );
212 }
213 
214 void QgsHighlight::paintPolygon( QPainter *p, const QgsPolygonXY &polygon )
215 {
216  // OddEven fill rule by default
217  QPainterPath path;
218 
219  p->setPen( mPen );
220  p->setBrush( mBrush );
221 
222  for ( const auto &sourceRing : polygon )
223  {
224  if ( sourceRing.empty() )
225  continue;
226 
227  QPolygonF ring;
228  ring.reserve( sourceRing.size() + 1 );
229 
230  QPointF lastVertex;
231  for ( const auto &sourceVertex : sourceRing )
232  {
233  //adding point only if it is more than a pixel apart from the previous one
234  const QPointF curVertex = toCanvasCoordinates( sourceVertex ) - pos();
235  if ( ring.isEmpty() || std::abs( ring.back().x() - curVertex.x() ) > 1 || std::abs( ring.back().y() - curVertex.y() ) > 1 )
236  {
237  ring.push_back( curVertex );
238  }
239  lastVertex = curVertex;
240  }
241 
242  ring.push_back( ring.at( 0 ) );
243 
244  path.addPolygon( ring );
245  }
246 
247  p->drawPath( path );
248 }
249 
251 {
252  if ( isVisible() ) updateRect();
253 }
254 
255 void QgsHighlight::paint( QPainter *p )
256 {
257  if ( mGeometry )
258  {
259  p->setPen( mPen );
260  p->setBrush( mBrush );
261 
262  switch ( mGeometry->type() )
263  {
265  {
266  if ( !mGeometry->isMultipart() )
267  {
268  paintPoint( p, mGeometry->asPoint() );
269  }
270  else
271  {
272  QgsMultiPointXY m = mGeometry->asMultiPoint();
273  for ( int i = 0; i < m.size(); i++ )
274  {
275  paintPoint( p, m[i] );
276  }
277  }
278  }
279  break;
280 
282  {
283  if ( !mGeometry->isMultipart() )
284  {
285  paintLine( p, mGeometry->asPolyline() );
286  }
287  else
288  {
289  QgsMultiPolylineXY m = mGeometry->asMultiPolyline();
290 
291  for ( int i = 0; i < m.size(); i++ )
292  {
293  paintLine( p, m[i] );
294  }
295  }
296  break;
297  }
298 
300  {
301  if ( !mGeometry->isMultipart() )
302  {
303  paintPolygon( p, mGeometry->asPolygon() );
304  }
305  else
306  {
307  QgsMultiPolygonXY m = mGeometry->asMultiPolygon();
308  for ( int i = 0; i < m.size(); i++ )
309  {
310  paintPolygon( p, m[i] );
311  }
312  }
313  break;
314  }
315 
318  return;
319  }
320  }
321  else if ( mFeature.hasGeometry() )
322  {
323  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mLayer );
324  if ( !layer )
325  return;
326  QgsMapSettings mapSettings = mMapCanvas->mapSettings();
327  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
329 
330 
331  // Because lower level outlines must be covered by upper level fill color
332  // we render first with temporary opaque color, which is then replaced
333  // by final transparent fill color.
334  QColor tmpColor( 255, 0, 0, 255 );
335  QColor tmpFillColor( 0, 255, 0, 255 );
336 
337  std::unique_ptr< QgsFeatureRenderer > renderer = createRenderer( context, tmpColor, tmpFillColor );
338  if ( layer && renderer )
339  {
340 
341  QSize imageSize( mMapCanvas->mapSettings().outputSize() );
342  QImage image = QImage( imageSize.width(), imageSize.height(), QImage::Format_ARGB32 );
343  image.fill( 0 );
344  QPainter imagePainter( &image );
345  imagePainter.setRenderHint( QPainter::Antialiasing, true );
346 
347  context.setPainter( &imagePainter );
348 
349  renderer->startRender( context, layer->fields() );
350  context.expressionContext().setFeature( mFeature );
351  renderer->renderFeature( mFeature, context );
352  renderer->stopRender( context );
353 
354  imagePainter.end();
355 
356  // true output color
357  int penRed = mPen.color().red();
358  int penGreen = mPen.color().green();
359  int penBlue = mPen.color().blue();
360  // coefficient to subtract alpha using green (temporary fill)
361  double k = ( 255. - mBrush.color().alpha() ) / 255.;
362  QRgb *line = nullptr;
363  for ( int r = 0; r < image.height(); r++ )
364  {
365  line = reinterpret_cast<QRgb *>( image.scanLine( r ) );
366  for ( int c = 0; c < image.width(); c++ )
367  {
368  int alpha = qAlpha( line[c] );
369  if ( alpha > 0 )
370  {
371  int green = qGreen( line[c] );
372  line[c] = qRgba( penRed, penGreen, penBlue, qBound<int>( 0, alpha - ( green * k ), 255 ) );
373  }
374  }
375  }
376 
377  p->drawImage( 0, 0, image );
378  }
379  }
380 }
381 
383 {
384  if ( mGeometry )
385  {
386  QgsRectangle r = mGeometry->boundingBox();
387 
388  if ( r.isEmpty() )
389  {
390  double d = mMapCanvas->extent().width() * 0.005;
391  r.setXMinimum( r.xMinimum() - d );
392  r.setYMinimum( r.yMinimum() - d );
393  r.setXMaximum( r.xMaximum() + d );
394  r.setYMaximum( r.yMaximum() + d );
395  }
396 
397  setRect( r );
398  setVisible( mGeometry );
399  }
400  else if ( mFeature.hasGeometry() )
401  {
402  // We are currently using full map canvas extent for two reasons:
403  // 1) currently there is no method in QgsFeatureRenderer to get rendered feature
404  // bounding box
405  // 2) using different extent would result in shifted fill patterns
406 
407  // This is an hack to pass QgsMapCanvasItem::setRect what it
408  // expects (encoding of position and size of the item)
410  QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
411  double res = m2p.mapUnitsPerPixel();
412  QSizeF imageSize = mMapCanvas->mapSettings().outputSize();
413  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + imageSize.width()*res, topLeft.y() - imageSize.height()*res );
414  setRect( rect );
415 
416  setVisible( true );
417  }
418  else
419  {
420  setRect( QgsRectangle() );
421  }
422 }
QgsUnitTypes::RenderUnit widthUnit() const
Returns the units for the line&#39;s width.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Base class for all map layer types.
Definition: qgsmaplayer.h:63
QgsHighlight(QgsMapCanvas *mapCanvas, const QgsGeometry &geom, QgsMapLayer *layer)
Constructor for QgsHighlight.
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:425
void updatePosition() override
called on changed extent or resize event to update position of the item
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry() )...
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:134
Simple marker symbol layer, consisting of a rendered shape with solid fill color and an stroke...
virtual void setWidth(double width)
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:150
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
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.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void setStrokeWidth(double w)
Sets the width of the marker&#39;s stroke.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
const QgsMapToPixel & mapToPixel() const
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition: qgsgeometry.h:74
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer&#39;s CRS to destination CRS.
QgsRectangle rect() const
returns canvas item rectangle in map units
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
~QgsHighlight() override
QVector< QgsPolygonXY > QgsMultiPolygonXY
A collection of QgsPolygons that share a common collection of attributes.
Definition: qgsgeometry.h:85
void setStrokeWidth(double strokeWidth)
The QgsMapSettings class contains configuration for rendering of the map.
QgsMultiPolygonXY asMultiPolygon() const
Returns contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty list.
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
QColor color() const
Returns the line/stroke color.
Definition: qgshighlight.h:97
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:36
QgsFields fields() const FINAL
Returns the list of fields of this layer.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
QSize outputSize() const
Returns the size of the resulting map image.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
QgsUnitTypes::RenderUnit strokeWidthUnit() const
Returns the unit for the width of the marker&#39;s stroke.
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:139
void updateRect()
recalculates needed rectangle
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
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.
double mapUnitsPerPixel() const
Returns current map units per pixel.
QgsFeatureRenderer * renderer()
Returns renderer.
QgsMultiPolylineXY asMultiPolyline() const
Returns contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
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:340
double strokeWidth() const
Returns the width of the marker&#39;s stroke.
double x
Definition: qgspointxy.h:47
virtual void setStrokeColor(const QColor &color)
Set stroke color.
A class to represent a vector.
Definition: qgsvector.h:28
QgsExpressionContext & expressionContext()
Gets the expression context.
int width() const
Returns the stroke width.
Definition: qgshighlight.h:122
virtual double width() const
Returns the estimated width for the line symbol layer.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
virtual void setFillColor(const QColor &color)
Set fill color.
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:144
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QgsMapCanvas * mMapCanvas
pointer to map canvas
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
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
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
Class for doing transforms between two map coordinate systems.
QgsMultiPointXY asMultiPoint() const
Returns contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty lis...
QgsMapLayer * layer() const
Returns the layer for which this highlight has been created.
Definition: qgshighlight.h:154
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
QgsGeometry geometry
Definition: qgsfeature.h:67
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
QColor fillColor() const
Returns the fill color.
Definition: qgshighlight.h:108
QPointF toCanvasCoordinates(const QgsPointXY &point) const
transformation from map coordinates to screen coordinates
Represents a vector layer which manages a vector based data sets.
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:129
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:110
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void setWidth(int width)
Set stroke width.
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.