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