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