Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgspallabeling.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspallabeling.cpp
3  Smart labeling for vector layers
4  -------------------
5  begin : June 2009
6  copyright : (C) Martin Dobias
7  email : wonder.sk at gmail.com
8 
9  ***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgspallabeling.h"
19 
20 #include <iostream>
21 #include <list>
22 
23 #include <pal/pal.h>
24 #include <pal/feature.h>
25 #include <pal/layer.h>
26 #include <pal/palgeometry.h>
27 #include <pal/palexception.h>
28 #include <pal/problem.h>
29 #include <pal/labelposition.h>
30 
31 #include <geos_c.h>
32 
33 #include <cmath>
34 
35 #include <QByteArray>
36 #include <QString>
37 #include <QFontMetrics>
38 #include <QTime>
39 #include <QPainter>
40 
41 #include "qgsdiagram.h"
42 #include "qgsdiagramrendererv2.h"
43 #include "qgslabelsearchtree.h"
44 #include <qgslogger.h>
45 #include <qgsvectorlayer.h>
46 #include <qgsmaplayerregistry.h>
47 #include <qgsvectordataprovider.h>
48 #include <qgsgeometry.h>
49 #include <qgsmaprenderer.h>
50 
51 
52 using namespace pal;
53 
54 
55 class QgsPalGeometry : public PalGeometry
56 {
57  public:
58  QgsPalGeometry( int id, QString text, GEOSGeometry* g ): mG( g ), mText( text ), mId( id ), mInfo( NULL ), mIsDiagram( false )
59  {
60  mStrId = QByteArray::number( id );
61  }
62 
64  {
65  if ( mG ) GEOSGeom_destroy( mG );
66  delete mInfo;
67  }
68 
69  // getGeosGeometry + releaseGeosGeometry is called twice: once when adding, second time when labeling
70 
71  GEOSGeometry* getGeosGeometry()
72  {
73  return mG;
74  }
75  void releaseGeosGeometry( GEOSGeometry* /*geom*/ )
76  {
77  // nothing here - we'll delete the geometry in destructor
78  }
79 
80  const char* strId() { return mStrId.data(); }
81  QString text() { return mText; }
82 
83  pal::LabelInfo* info( QFontMetricsF* fm, const QgsMapToPixel* xform, double fontScale )
84  {
85  if ( mInfo ) return mInfo;
86 
87  // create label info!
88  QgsPoint ptZero = xform->toMapCoordinates( 0, 0 );
89  QgsPoint ptSize = xform->toMapCoordinatesF( 0.0, -fm->height() / fontScale );
90 
91  mInfo = new pal::LabelInfo( mText.count(), ptSize.y() - ptZero.y() );
92  for ( int i = 0; i < mText.count(); i++ )
93  {
94  mInfo->char_info[i].chr = mText[i].unicode();
95  ptSize = xform->toMapCoordinatesF( fm->width( mText[i] ) / fontScale , 0.0 );
96  mInfo->char_info[i].width = ptSize.x() - ptZero.x();
97  }
98  return mInfo;
99  }
100 
101  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& dataDefinedValues() const { return mDataDefinedValues; }
102  void addDataDefinedValue( QgsPalLayerSettings::DataDefinedProperties p, QVariant v ) { mDataDefinedValues.insert( p, v ); }
103 
104  void setIsDiagram( bool d ) { mIsDiagram = d; }
105  bool isDiagram() const { return mIsDiagram; }
106 
107  void addDiagramAttribute( int index, QVariant value ) { mDiagramAttributes.insert( index, value ); }
108  const QgsAttributeMap& diagramAttributes() { return mDiagramAttributes; }
109 
110  protected:
111  GEOSGeometry* mG;
112  QString mText;
113  QByteArray mStrId;
114  int mId;
115  LabelInfo* mInfo;
118  QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant > mDataDefinedValues;
119 
122 };
123 
124 // -------------
125 
127  : palLayer( NULL ), fontMetrics( NULL ), ct( NULL )
128 {
130  placementFlags = 0;
131  //textFont = QFont();
132  textColor = Qt::black;
133  enabled = false;
134  priority = 5;
135  obstacle = true;
136  dist = 0;
137  scaleMin = 0;
138  scaleMax = 0;
139  bufferSize = 1;
140  bufferColor = Qt::white;
141  labelPerPart = false;
142  mergeLines = false;
143  multiLineLabels = false;
144  minFeatureSize = 0.0;
145  vectorScaleFactor = 1.0;
146  rasterCompressFactor = 1.0;
147  addDirectionSymbol = false;
148  fontSizeInMapUnits = false;
149  distInMapUnits = false;
150 }
151 
153 {
154  // copy only permanent stuff
155  fieldName = s.fieldName;
156  placement = s.placement;
158  textFont = s.textFont;
159  textColor = s.textColor;
160  enabled = s.enabled;
161  priority = s.priority;
162  obstacle = s.obstacle;
163  dist = s.dist;
164  scaleMin = s.scaleMin;
165  scaleMax = s.scaleMax;
177 
179  fontMetrics = NULL;
180  ct = NULL;
181 }
182 
183 
185 {
186  // pal layer is deleted internally in PAL
187 
188  delete fontMetrics;
189  delete ct;
190 }
191 
192 static QColor _readColor( QgsVectorLayer* layer, QString property )
193 {
194  int r = layer->customProperty( property + "R" ).toInt();
195  int g = layer->customProperty( property + "G" ).toInt();
196  int b = layer->customProperty( property + "B" ).toInt();
197  return QColor( r, g, b );
198 }
199 
200 static void _writeColor( QgsVectorLayer* layer, QString property, QColor color )
201 {
202  layer->setCustomProperty( property + "R", color.red() );
203  layer->setCustomProperty( property + "G", color.green() );
204  layer->setCustomProperty( property + "B", color.blue() );
205 }
206 
207 static void _writeDataDefinedPropertyMap( QgsVectorLayer* layer, const QMap< QgsPalLayerSettings::DataDefinedProperties, int >& propertyMap )
208 {
209  if ( !layer )
210  {
211  return;
212  }
213 
214  for ( int i = 0; i < 15; ++i )
215  {
216  QMap< QgsPalLayerSettings::DataDefinedProperties, int >::const_iterator it = propertyMap.find(( QgsPalLayerSettings::DataDefinedProperties )i );
217  QVariant propertyValue;
218  if ( it == propertyMap.constEnd() )
219  {
220  propertyValue = QVariant(); //we cannot delete properties, so we just insert an invalid variant
221  }
222  else
223  {
224  propertyValue = *it;
225  }
226  layer->setCustomProperty( "labeling/dataDefinedProperty" + QString::number( i ), propertyValue );
227  }
228 }
229 
231  QMap< QgsPalLayerSettings::DataDefinedProperties, int >& propertyMap )
232 {
233  QVariant propertyField = layer->customProperty( "labeling/dataDefinedProperty" + QString::number( p ) );
234  bool conversionOk;
235  int fieldIndex;
236 
237  if ( propertyField.isValid() )
238  {
239  fieldIndex = propertyField.toInt( &conversionOk );
240  if ( conversionOk )
241  {
242  propertyMap.insert( p, fieldIndex );
243  }
244  }
245 }
246 
247 static void _readDataDefinedPropertyMap( QgsVectorLayer* layer, QMap< QgsPalLayerSettings::DataDefinedProperties, int >& propertyMap )
248 {
249  if ( !layer )
250  {
251  return;
252  }
253 
269 }
270 
272 {
273  if ( layer->customProperty( "labeling" ).toString() != QString( "pal" ) )
274  return; // there's no information available
275 
276  fieldName = layer->customProperty( "labeling/fieldName" ).toString();
277  placement = ( Placement ) layer->customProperty( "labeling/placement" ).toInt();
278  placementFlags = layer->customProperty( "labeling/placementFlags" ).toUInt();
279  QString fontFamily = layer->customProperty( "labeling/fontFamily" ).toString();
280  double fontSize = layer->customProperty( "labeling/fontSize" ).toDouble();
281  int fontWeight = layer->customProperty( "labeling/fontWeight" ).toInt();
282  bool fontItalic = layer->customProperty( "labeling/fontItalic" ).toBool();
283  textFont = QFont( fontFamily, fontSize, fontWeight, fontItalic );
284  textFont.setUnderline( layer->customProperty( "labeling/fontUnderline" ).toBool() );
285  textFont.setStrikeOut( layer->customProperty( "labeling/fontStrikeout" ).toBool() );
286  textFont.setPointSizeF( fontSize ); //double precision needed because of map units
287  textColor = _readColor( layer, "labeling/textColor" );
288  enabled = layer->customProperty( "labeling/enabled" ).toBool();
289  priority = layer->customProperty( "labeling/priority" ).toInt();
290  obstacle = layer->customProperty( "labeling/obstacle" ).toBool();
291  dist = layer->customProperty( "labeling/dist" ).toDouble();
292  scaleMin = layer->customProperty( "labeling/scaleMin" ).toInt();
293  scaleMax = layer->customProperty( "labeling/scaleMax" ).toInt();
294  bufferSize = layer->customProperty( "labeling/bufferSize" ).toDouble();
295  bufferColor = _readColor( layer, "labeling/bufferColor" );
296  labelPerPart = layer->customProperty( "labeling/labelPerPart" ).toBool();
297  mergeLines = layer->customProperty( "labeling/mergeLines" ).toBool();
298  multiLineLabels = layer->customProperty( "labeling/multiLineLabels" ).toBool();
299  addDirectionSymbol = layer->customProperty( "labeling/addDirectionSymbol" ).toBool();
300  minFeatureSize = layer->customProperty( "labeling/minFeatureSize" ).toDouble();
301  fontSizeInMapUnits = layer->customProperty( "labeling/fontSizeInMapUnits" ).toBool();
302  distInMapUnits = layer->customProperty( "labeling/distInMapUnits" ).toBool();
304 }
305 
307 {
308  // this is a mark that labeling information is present
309  layer->setCustomProperty( "labeling", "pal" );
310 
311  layer->setCustomProperty( "labeling/fieldName", fieldName );
312  layer->setCustomProperty( "labeling/placement", placement );
313  layer->setCustomProperty( "labeling/placementFlags", ( unsigned int )placementFlags );
314 
315  layer->setCustomProperty( "labeling/fontFamily", textFont.family() );
316  layer->setCustomProperty( "labeling/fontSize", textFont.pointSizeF() );
317  layer->setCustomProperty( "labeling/fontWeight", textFont.weight() );
318  layer->setCustomProperty( "labeling/fontItalic", textFont.italic() );
319  layer->setCustomProperty( "labeling/fontStrikeout", textFont.strikeOut() );
320  layer->setCustomProperty( "labeling/fontUnderline", textFont.underline() );
321 
322  _writeColor( layer, "labeling/textColor", textColor );
323  layer->setCustomProperty( "labeling/enabled", enabled );
324  layer->setCustomProperty( "labeling/priority", priority );
325  layer->setCustomProperty( "labeling/obstacle", obstacle );
326  layer->setCustomProperty( "labeling/dist", dist );
327  layer->setCustomProperty( "labeling/scaleMin", scaleMin );
328  layer->setCustomProperty( "labeling/scaleMax", scaleMax );
329  layer->setCustomProperty( "labeling/bufferSize", bufferSize );
330  _writeColor( layer, "labeling/bufferColor", bufferColor );
331  layer->setCustomProperty( "labeling/labelPerPart", labelPerPart );
332  layer->setCustomProperty( "labeling/mergeLines", mergeLines );
333  layer->setCustomProperty( "labeling/multiLineLabels", multiLineLabels );
334  layer->setCustomProperty( "labeling/addDirectionSymbol", addDirectionSymbol );
335  layer->setCustomProperty( "labeling/minFeatureSize", minFeatureSize );
336  layer->setCustomProperty( "labeling/fontSizeInMapUnits", fontSizeInMapUnits );
337  layer->setCustomProperty( "labeling/distInMapUnits", distInMapUnits );
339 }
340 
342 {
343  dataDefinedProperties.insert( p, attributeIndex );
344 }
345 
347 {
348  dataDefinedProperties.remove( p );
349 }
350 
351 bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext& ct, QgsGeometry* geom, double minSize ) const
352 {
353  if ( minSize <= 0 )
354  {
355  return true;
356  }
357 
358  if ( !geom )
359  {
360  return false;
361  }
362 
363  QGis::GeometryType featureType = geom->type();
364  if ( featureType == QGis::Point ) //minimum size does not apply to point features
365  {
366  return true;
367  }
368 
369  double mapUnitsPerMM = ct.mapToPixel().mapUnitsPerPixel() * ct.scaleFactor();
370  if ( featureType == QGis::Line )
371  {
372  double length = geom->length();
373  if ( length >= 0.0 )
374  {
375  return ( length >= ( minSize * mapUnitsPerMM ) );
376  }
377  }
378  else if ( featureType == QGis::Polygon )
379  {
380  double area = geom->area();
381  if ( area >= 0.0 )
382  {
383  return ( sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
384  }
385  }
386  return true; //should never be reached. Return true in this case to label such geometries anyway.
387 }
388 
389 void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF* fm, QString text, double& labelX, double& labelY )
390 {
391  if ( !fm )
392  {
393  return;
394  }
395 
396  if ( addDirectionSymbol && !multiLineLabels && placement == QgsPalLayerSettings::Line ) //consider the space needed for the direction symbol
397  {
398  text.append( ">" );
399  }
400  QRectF labelRect = fm->boundingRect( text );
401  double w, h;
402  if ( !multiLineLabels )
403  {
404  w = labelRect.width() / rasterCompressFactor;
405  h = labelRect.height() / rasterCompressFactor;
406  }
407  else
408  {
409  QStringList multiLineSplit = text.split( "\n" );
410  h = fm->height() * multiLineSplit.size() / rasterCompressFactor;
411  w = 0;
412  for ( int i = 0; i < multiLineSplit.size(); ++i )
413  {
414  double width = fm->width( multiLineSplit.at( i ) );
415  if ( width > w )
416  {
417  w = width;
418  }
420  }
421  }
422  QgsPoint ptSize = xform->toMapCoordinatesF( w, h );
423 
424  labelX = qAbs( ptSize.x() - ptZero.x() );
425  labelY = qAbs( ptSize.y() - ptZero.y() );
426 }
427 
429 {
430  QString labelText = f.attributeMap()[fieldIndex].toString();
431  double labelX, labelY; // will receive label size
432  QFont labelFont = textFont;
433 
434  //data defined label size?
435  QMap< DataDefinedProperties, int >::const_iterator it = dataDefinedProperties.find( QgsPalLayerSettings::Size );
436  if ( it != dataDefinedProperties.constEnd() )
437  {
438  //find out size
439  QVariant size = f.attributeMap().value( *it );
440  if ( size.isValid() )
441  {
442  double sizeDouble = size.toDouble();
443  if ( sizeDouble <= 0 )
444  {
445  return;
446  }
447  labelFont.setPixelSize( sizeToPixel( sizeDouble, context ) );
448  }
449  QFontMetricsF labelFontMetrics( labelFont );
450  calculateLabelSize( &labelFontMetrics, labelText, labelX, labelY );
451  }
452  else
453  {
454  calculateLabelSize( fontMetrics, labelText, labelX, labelY );
455  }
456 
457  QgsGeometry* geom = f.geometry();
458 
459  if ( ct ) // reproject the geometry if necessary
460  geom->transform( *ct );
461 
462  GEOSGeometry* geos_geom = geom->asGeos();
463  if ( geos_geom == NULL )
464  return; // invalid geometry
465 
466  if ( !checkMinimumSizeMM( context, geom, minFeatureSize ) )
467  {
468  return;
469  }
470 
471  //data defined position / alignment / rotation?
472  bool dataDefinedPosition = false;
473  bool dataDefinedRotation = false;
474  double xPos = 0.0, yPos = 0.0, angle = 0.0;
475  bool ddXPos, ddYPos;
476 
477  QMap< DataDefinedProperties, int >::const_iterator dPosXIt = dataDefinedProperties.find( QgsPalLayerSettings::PositionX );
478  if ( dPosXIt != dataDefinedProperties.constEnd() )
479  {
480  QMap< DataDefinedProperties, int >::const_iterator dPosYIt = dataDefinedProperties.find( QgsPalLayerSettings::PositionY );
481  if ( dPosYIt != dataDefinedProperties.constEnd() )
482  {
483  //data defined position. But field values could be NULL -> positions will be generated by PAL
484  xPos = f.attributeMap().value( *dPosXIt ).toDouble( &ddXPos );
485  yPos = f.attributeMap().value( *dPosYIt ).toDouble( &ddYPos );
486 
487  if ( ddXPos && ddYPos )
488  {
489  dataDefinedPosition = true;
490  //x/y shift in case of alignment
491  double xdiff = 0;
492  double ydiff = 0;
493 
494  //horizontal alignment
495  QMap< DataDefinedProperties, int >::const_iterator haliIt = dataDefinedProperties.find( QgsPalLayerSettings::Hali );
496  if ( haliIt != dataDefinedProperties.end() )
497  {
498  QString haliString = f.attributeMap().value( *haliIt ).toString();
499  if ( haliString.compare( "Center", Qt::CaseInsensitive ) == 0 )
500  {
501  xdiff -= labelX / 2.0;
502  }
503  else if ( haliString.compare( "Right", Qt::CaseInsensitive ) == 0 )
504  {
505  xdiff -= labelX;
506  }
507  }
508 
509  //vertical alignment
510  QMap< DataDefinedProperties, int >::const_iterator valiIt = dataDefinedProperties.find( QgsPalLayerSettings::Vali );
511  if ( valiIt != dataDefinedProperties.constEnd() )
512  {
513  QString valiString = f.attributeMap().value( *valiIt ).toString();
514  if ( valiString.compare( "Bottom", Qt::CaseInsensitive ) != 0 )
515  {
516  if ( valiString.compare( "Top", Qt::CaseInsensitive ) == 0 || valiString.compare( "Cap", Qt::CaseInsensitive ) == 0 )
517  {
518  ydiff -= labelY;
519  }
520  else
521  {
522  QFontMetrics labelFontMetrics( labelFont );
523  double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
524 
525  if ( valiString.compare( "Base", Qt::CaseInsensitive ) == 0 )
526  {
527  ydiff -= labelY * descentRatio;
528  }
529  else if ( valiString.compare( "Half", Qt::CaseInsensitive ) == 0 )
530  {
531  ydiff -= labelY * descentRatio;
532  ydiff -= labelY * 0.5 * ( 1 - descentRatio );
533  }
534  }
535  }
536  }
537 
538  //data defined rotation?
539  QMap< DataDefinedProperties, int >::const_iterator rotIt = dataDefinedProperties.find( QgsPalLayerSettings::Rotation );
540  if ( rotIt != dataDefinedProperties.constEnd() )
541  {
542  dataDefinedRotation = true;
543  angle = f.attributeMap().value( *rotIt ).toDouble() * M_PI / 180;
544  //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
545  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
546  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
547  xdiff = xd;
548  ydiff = yd;
549  }
550 
551  //project xPos and yPos from layer to map CRS
552  double z = 0;
553  if ( ct )
554  {
555  ct->transformInPlace( xPos, yPos, z );
556  }
557 
558  yPos += ydiff;
559  xPos += xdiff;
560 
561  }
562  }
563  }
564 
565  QgsPalGeometry* lbl = new QgsPalGeometry( f.id(), labelText, GEOSGeom_clone( geos_geom ) );
566 
567  // record the created geometry - it will be deleted at the end.
568  geometries.append( lbl );
569 
570  // register feature to the layer
571  try
572  {
573  if ( !palLayer->registerFeature( lbl->strId(), lbl, labelX, labelY, labelText.toUtf8().constData(),
574  xPos, yPos, dataDefinedPosition, angle, dataDefinedRotation ) )
575  return;
576  }
577  catch ( std::exception* e )
578  {
579  QgsDebugMsg( QString( "Ignoring feature %1 due PAL exception: " ).arg( f.id() ) + QString::fromLatin1( e->what() ) );
580  return;
581  }
582 
583  // TODO: only for placement which needs character info
584  pal::Feature* feat = palLayer->getFeature( lbl->strId() );
585  feat->setLabelInfo( lbl->info( fontMetrics, xform, rasterCompressFactor ) );
586 
587  // TODO: allow layer-wide feature dist in PAL...?
588 
589  //data defined label-feature distance?
590  double distance = dist;
591  QMap< DataDefinedProperties, int >::const_iterator dDistIt = dataDefinedProperties.find( QgsPalLayerSettings::LabelDistance );
592  if ( dDistIt != dataDefinedProperties.constEnd() )
593  {
594  distance = f.attributeMap().value( *dDistIt ).toDouble();
595  }
596 
597  if ( distance != 0 )
598  {
599  if ( distInMapUnits ) //convert distance from mm/map units to pixels
600  {
601  distance /= context.mapToPixel().mapUnitsPerPixel();
602  }
603  else //mm
604  {
605  distance *= vectorScaleFactor;
606  }
607  feat->setDistLabel( qAbs( ptOne.x() - ptZero.x() )* distance );
608  }
609 
610  //add parameters for data defined labeling to QgsPalGeometry
611  QMap< DataDefinedProperties, int >::const_iterator dIt = dataDefinedProperties.constBegin();
612  for ( ; dIt != dataDefinedProperties.constEnd(); ++dIt )
613  {
614  lbl->addDataDefinedValue( dIt.key(), f.attributeMap()[dIt.value()] );
615  }
616 }
617 
618 int QgsPalLayerSettings::sizeToPixel( double size, const QgsRenderContext& c ) const
619 {
620  double pixelSize;
621  if ( fontSizeInMapUnits )
622  {
623  pixelSize = size / c.mapToPixel().mapUnitsPerPixel() * c.rasterScaleFactor();
624  }
625  else //font size in points
626  {
627  // set font size from points to output size
628  pixelSize = 0.3527 * size * c.scaleFactor() * c.rasterScaleFactor();
629  }
630  return ( int )( pixelSize + 0.5 );
631 }
632 
633 
634 // -------------
635 
637  : mMapRenderer( NULL ), mPal( NULL )
638 {
639 
640  // find out engine defaults
641  Pal p;
642  mCandPoint = p.getPointP();
643  mCandLine = p.getLineP();
644  mCandPolygon = p.getPolyP();
645 
646  switch ( p.getSearch() )
647  {
648  case CHAIN: mSearch = Chain; break;
649  case POPMUSIC_TABU: mSearch = Popmusic_Tabu; break;
650  case POPMUSIC_CHAIN: mSearch = Popmusic_Chain; break;
651  case POPMUSIC_TABU_CHAIN: mSearch = Popmusic_Tabu_Chain; break;
652  case FALP: mSearch = Falp; break;
653  }
654 
655  mShowingCandidates = false;
656  mShowingAllLabels = false;
657 
659 }
660 
661 
663 {
664  // make sure we've freed everything
665  exit();
666  delete mLabelSearchTree;
667  mLabelSearchTree = NULL;
668 }
669 
670 
672 {
673  QgsPalLayerSettings lyrTmp;
674  lyrTmp.readFromLayer( layer );
675  return lyrTmp.enabled;
676 }
677 
678 int QgsPalLabeling::prepareLayer( QgsVectorLayer* layer, QSet<int>& attrIndices, QgsRenderContext& ctx )
679 {
680  Q_ASSERT( mMapRenderer != NULL );
681 
682  // start with a temporary settings class, find out labeling info
683  QgsPalLayerSettings lyrTmp;
684  lyrTmp.readFromLayer( layer );
685 
686  if ( !lyrTmp.enabled )
687  return 0;
688 
689  // find out which field will be needed
690  int fldIndex = layer->fieldNameIndex( lyrTmp.fieldName );
691  if ( fldIndex == -1 )
692  return 0;
693  attrIndices.insert( fldIndex );
694 
695  //add indices of data defined fields
696  QMap< QgsPalLayerSettings::DataDefinedProperties, int >::const_iterator dIt = lyrTmp.dataDefinedProperties.constBegin();
697  for ( ; dIt != lyrTmp.dataDefinedProperties.constEnd(); ++dIt )
698  {
699  attrIndices.insert( dIt.value() );
700  }
701 
702  // add layer settings to the pallabeling hashtable: <QgsVectorLayer*, QgsPalLayerSettings>
703  mActiveLayers.insert( layer, lyrTmp );
704  // start using the reference to the layer in hashtable instead of local instance
706 
707  // how to place the labels
708  Arrangement arrangement;
709  switch ( lyr.placement )
710  {
711  case QgsPalLayerSettings::AroundPoint: arrangement = P_POINT; break;
712  case QgsPalLayerSettings::OverPoint: arrangement = P_POINT_OVER; break;
713  case QgsPalLayerSettings::Line: arrangement = P_LINE; break;
714  case QgsPalLayerSettings::Curved: arrangement = P_CURVED; break;
715  case QgsPalLayerSettings::Horizontal: arrangement = P_HORIZ; break;
716  case QgsPalLayerSettings::Free: arrangement = P_FREE; break;
717  default: Q_ASSERT( "unsupported placement" && 0 ); return 0; break;
718  }
719 
720  // create the pal layer
721  double priority = 1 - lyr.priority / 10.0; // convert 0..10 --> 1..0
722  double min_scale = -1, max_scale = -1;
723  if ( lyr.scaleMin != 0 && lyr.scaleMax != 0 )
724  {
725  min_scale = lyr.scaleMin;
726  max_scale = lyr.scaleMax;
727  }
728 
729  Layer* l = mPal->addLayer( layer->id().toUtf8().data(),
730  min_scale, max_scale, arrangement,
731  METER, priority, lyr.obstacle, true, true );
732 
733  if ( lyr.placementFlags )
734  l->setArrangementFlags( lyr.placementFlags );
735 
736  // set label mode (label per feature is the default)
737  l->setLabelMode( lyr.labelPerPart ? Layer::LabelPerFeaturePart : Layer::LabelPerFeature );
738 
739  // set whether adjacent lines should be merged
740  l->setMergeConnectedLines( lyr.mergeLines );
741 
742  lyr.textFont.setPixelSize( lyr.sizeToPixel( lyr.textFont.pointSizeF(), ctx ) );
743 
744  //raster and vector scale factors
745  lyr.vectorScaleFactor = ctx.scaleFactor();
747 
748  // save the pal layer to our layer context (with some additional info)
749  lyr.palLayer = l;
750  lyr.fieldIndex = fldIndex;
751  lyr.fontMetrics = new QFontMetricsF( lyr.textFont );
752 
755  lyr.ct = new QgsCoordinateTransform( layer->crs(), mMapRenderer->destinationCrs() );
756  else
757  lyr.ct = NULL;
758  lyr.ptZero = lyr.xform->toMapCoordinates( 0, 0 );
759  lyr.ptOne = lyr.xform->toMapCoordinates( 1, 0 );
760 
761  return 1; // init successful
762 }
763 
765 {
766  Layer* l = mPal->addLayer( layer->id().append( "d" ).toUtf8().data(), -1, -1, pal::Arrangement( s->placement ), METER, s->priority, s->obstacle, true, true );
767  l->setArrangementFlags( s->placementFlags );
768 
769  s->palLayer = l;
771  s->ct = new QgsCoordinateTransform( layer->crs(), mMapRenderer->destinationCrs() );
772  else
773  s->ct = NULL;
775  mActiveDiagramLayers.insert( layer, *s );
776  return 1;
777 }
778 
780 {
782  lyr.registerFeature( f, context );
783 }
784 
786 {
787  //get diagram layer settings, diagram renderer
788  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator layerIt = mActiveDiagramLayers.find( layer );
789  if ( layerIt == mActiveDiagramLayers.constEnd() )
790  {
791  return;
792  }
793 
794  //convert geom to geos
795  QgsGeometry* geom = feat.geometry();
796 
797  if ( layerIt.value().ct && !willUseLayer( layer ) ) // reproject the geometry if feature not already transformed for labeling
798  {
799  geom->transform( *( layerIt.value().ct ) );
800  }
801 
802  GEOSGeometry* geos_geom = geom->asGeos();
803  if ( geos_geom == 0 )
804  {
805  return; // invalid geometry
806  }
807 
808  //create PALGeometry with diagram = true
809  QgsPalGeometry* lbl = new QgsPalGeometry( feat.id(), "", GEOSGeom_clone( geos_geom ) );
810  lbl->setIsDiagram( true );
811 
812  // record the created geometry - it will be deleted at the end.
813  layerIt.value().geometries.append( lbl );
814 
815  double diagramWidth = 0;
816  double diagramHeight = 0;
817  QgsDiagramRendererV2* dr = layerIt.value().renderer;
818  if ( dr )
819  {
820  QSizeF diagSize = dr->sizeMapUnits( feat.attributeMap(), context );
821  if ( diagSize.isValid() )
822  {
823  diagramWidth = diagSize.width();
824  diagramHeight = diagSize.height();
825  }
826 
827  //append the diagram attributes to lbl
828  QList<int> diagramAttrib = dr->diagramAttributes();
829  QList<int>::const_iterator diagAttIt = diagramAttrib.constBegin();
830  for ( ; diagAttIt != diagramAttrib.constEnd(); ++diagAttIt )
831  {
832  lbl->addDiagramAttribute( *diagAttIt, feat.attributeMap()[*diagAttIt] );
833  }
834  }
835 
836  // register feature to the layer
837  int ddColX = layerIt.value().xPosColumn;
838  int ddColY = layerIt.value().yPosColumn;
839  double ddPosX = 0.0;
840  double ddPosY = 0.0;
841  bool ddPos = ( ddColX >= 0 && ddColY >= 0 );
842  if ( ddPos )
843  {
844  bool posXOk, posYOk;
845  //data defined diagram position is always centered
846  ddPosX = feat.attributeMap()[ddColX].toDouble( &posXOk ) - diagramWidth / 2.0;
847  ddPosY = feat.attributeMap()[ddColY].toDouble( &posYOk ) - diagramHeight / 2.0;
848  if ( !posXOk || !posYOk )
849  {
850  ddPos = false;
851  }
852  else
853  {
854  const QgsCoordinateTransform* ct = layerIt.value().ct;
855  if ( ct )
856  {
857  double z = 0;
858  ct->transformInPlace( ddPosX, ddPosY, z );
859  }
860  }
861  }
862 
863  try
864  {
865  if ( !layerIt.value().palLayer->registerFeature( lbl->strId(), lbl, diagramWidth, diagramHeight, "", ddPosX, ddPosY, ddPos ) )
866  {
867  return;
868  }
869  }
870  catch ( std::exception* e )
871  {
872  QgsDebugMsg( QString( "Ignoring feature %1 due PAL exception: " ).arg( feat.id() ) + QString::fromLatin1( e->what() ) );
873  return;
874  }
875 
876  pal::Feature* palFeat = layerIt.value().palLayer->getFeature( lbl->strId() );
877  QgsPoint ptZero = layerIt.value().xform->toMapCoordinates( 0, 0 );
878  QgsPoint ptOne = layerIt.value().xform->toMapCoordinates( 1, 0 );
879  palFeat->setDistLabel( qAbs( ptOne.x() - ptZero.x() ) * layerIt.value().dist );
880 }
881 
882 
884 {
885  mMapRenderer = mr;
886 
887  // delete if exists already
888  if ( mPal )
889  delete mPal;
890 
891  mPal = new Pal;
892 
893  SearchMethod s;
894  switch ( mSearch )
895  {
896  default:
897  case Chain: s = CHAIN; break;
898  case Popmusic_Tabu: s = POPMUSIC_TABU; break;
899  case Popmusic_Chain: s = POPMUSIC_CHAIN; break;
900  case Popmusic_Tabu_Chain: s = POPMUSIC_TABU_CHAIN; break;
901  case Falp: s = FALP; break;
902  }
903  mPal->setSearch( s );
904 
905  // set number of candidates generated per feature
906  mPal->setPointP( mCandPoint );
907  mPal->setLineP( mCandLine );
908  mPal->setPolyP( mCandPolygon );
909 
910  mActiveLayers.clear();
911  mActiveDiagramLayers.clear();
912 }
913 
915 {
916  delete mPal;
917  mPal = NULL;
918  mMapRenderer = NULL;
919 }
920 
921 QgsPalLayerSettings& QgsPalLabeling::layer( const QString& layerName )
922 {
923  QHash<QgsVectorLayer*, QgsPalLayerSettings>::iterator lit;
924  for ( lit = mActiveLayers.begin(); lit != mActiveLayers.end(); ++lit )
925  {
926  if ( lit.key() && lit.key()->id() == layerName )
927  {
928  return lit.value();
929  }
930  }
931  return mInvalidLayerSettings;
932 }
933 
934 
936 {
937  Q_ASSERT( mMapRenderer != NULL );
938  QPainter* painter = context.painter();
939  QgsRectangle extent = context.extent();
940 
941  if ( mLabelSearchTree )
942  {
944  }
945 
946  QTime t;
947  t.start();
948 
949  // do the labeling itself
950  double scale = mMapRenderer->scale(); // scale denominator
951  QgsRectangle r = extent;
952  double bbox[] = { r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum() };
953 
954  std::list<LabelPosition*>* labels;
955  pal::Problem* problem;
956  try
957  {
958  problem = mPal->extractProblem( scale, bbox );
959  }
960  catch ( std::exception& e )
961  {
962  QgsDebugMsg( "PAL EXCEPTION :-( " + QString::fromLatin1( e.what() ) );
963  //mActiveLayers.clear(); // clean up
964  return;
965  }
966 
968 
969  // draw rectangles with all candidates
970  // this is done before actual solution of the problem
971  // before number of candidates gets reduced
972  mCandidates.clear();
973  if ( mShowingCandidates && problem )
974  {
975  painter->setPen( QColor( 0, 0, 0, 64 ) );
976  painter->setBrush( Qt::NoBrush );
977  for ( int i = 0; i < problem->getNumFeatures(); i++ )
978  {
979  for ( int j = 0; j < problem->getFeatureCandidateCount( i ); j++ )
980  {
981  pal::LabelPosition* lp = problem->getFeatureCandidate( i, j );
982 
983  drawLabelCandidateRect( lp, painter, xform );
984  }
985  }
986  }
987 
988  // find the solution
989  labels = mPal->solveProblem( problem, mShowingAllLabels );
990 
991  QgsDebugMsg( QString( "LABELING work: %1 ms ... labels# %2" ).arg( t.elapsed() ).arg( labels->size() ) );
992  t.restart();
993 
994  painter->setRenderHint( QPainter::Antialiasing );
995 
996  // draw the labels
997  std::list<LabelPosition*>::iterator it = labels->begin();
998  for ( ; it != labels->end(); ++it )
999  {
1000  QgsPalGeometry* palGeometry = dynamic_cast< QgsPalGeometry* >(( *it )->getFeaturePart()->getUserGeometry() );
1001  if ( !palGeometry )
1002  {
1003  continue;
1004  }
1005 
1006  //layer names
1007  QString layerNameUtf8 = QString::fromUtf8(( *it )->getLayerName() );
1008  if ( palGeometry->isDiagram() )
1009  {
1010  //render diagram
1011  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator dit = mActiveDiagramLayers.begin();
1012  for ( dit = mActiveDiagramLayers.begin(); dit != mActiveDiagramLayers.end(); ++dit )
1013  {
1014  if ( dit.key() && dit.key()->id().append( "d" ) == layerNameUtf8 )
1015  {
1016  QgsPoint outPt = xform->transform(( *it )->getX(), ( *it )->getY() );
1017  dit.value().renderer->renderDiagram( palGeometry->diagramAttributes(), context, QPointF( outPt.x(), outPt.y() ) );
1018  }
1019  }
1020 
1021  //insert into label search tree to manipulate position interactively
1022  if ( mLabelSearchTree )
1023  {
1024  //for diagrams, remove the additional 'd' at the end of the layer id
1025  QString layerId = layerNameUtf8;
1026  layerId.chop( 1 );
1027  mLabelSearchTree->insertLabel( *it, QString( palGeometry->strId() ).toInt(), layerId, true );
1028  }
1029  continue;
1030  }
1031 
1032  const QgsPalLayerSettings& lyr = layer( layerNameUtf8 );
1033  QFont fontForLabel = lyr.textFont;
1034  QColor fontColor = lyr.textColor;
1035  double bufferSize = lyr.bufferSize;
1036  QColor bufferColor = lyr.bufferColor;
1037 
1038  //apply data defined settings for the label
1039  //font size
1040  QVariant dataDefinedSize = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Size );
1041  if ( dataDefinedSize.isValid() )
1042  {
1043  fontForLabel.setPixelSize( lyr.sizeToPixel( dataDefinedSize.toDouble(), context ) );
1044  }
1045  //font color
1046  QVariant dataDefinedColor = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Color );
1047  if ( dataDefinedColor.isValid() )
1048  {
1049  fontColor.setNamedColor( dataDefinedColor.toString() );
1050  if ( !fontColor.isValid() )
1051  {
1052  fontColor = lyr.textColor;
1053  }
1054  }
1055  //font bold
1056  QVariant dataDefinedBold = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Bold );
1057  if ( dataDefinedBold.isValid() )
1058  {
1059  fontForLabel.setBold(( bool )dataDefinedBold.toInt() );
1060  }
1061  //font italic
1062  QVariant dataDefinedItalic = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Italic );
1063  if ( dataDefinedItalic.isValid() )
1064  {
1065  fontForLabel.setItalic(( bool ) dataDefinedItalic.toInt() );
1066  }
1067  //font underline
1068  QVariant dataDefinedUnderline = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Underline );
1069  if ( dataDefinedUnderline.isValid() )
1070  {
1071  fontForLabel.setUnderline(( bool ) dataDefinedUnderline.toInt() );
1072  }
1073  //font strikeout
1074  QVariant dataDefinedStrikeout = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Strikeout );
1075  if ( dataDefinedStrikeout.isValid() )
1076  {
1077  fontForLabel.setStrikeOut(( bool ) dataDefinedStrikeout.toInt() );
1078  }
1079  //font family
1080  QVariant dataDefinedFontFamily = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::Family );
1081  if ( dataDefinedFontFamily.isValid() )
1082  {
1083  fontForLabel.setFamily( dataDefinedFontFamily.toString() );
1084  }
1085  //buffer size
1086  QVariant dataDefinedBufferSize = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::BufferSize );
1087  if ( dataDefinedBufferSize.isValid() )
1088  {
1089  bufferSize = dataDefinedBufferSize.toDouble();
1090  }
1091 
1092  //buffer color
1093  QVariant dataDefinedBufferColor = palGeometry->dataDefinedValues().value( QgsPalLayerSettings::BufferColor );
1094  if ( dataDefinedBufferColor.isValid() )
1095  {
1096  bufferColor.setNamedColor( dataDefinedBufferColor.toString() );
1097  if ( !bufferColor.isValid() )
1098  {
1099  bufferColor = lyr.bufferColor;
1100  }
1101  }
1102 
1103  if ( lyr.bufferSize != 0 )
1104  drawLabel( *it, painter, fontForLabel, fontColor, xform, bufferSize, bufferColor, true );
1105 
1106  drawLabel( *it, painter, fontForLabel, fontColor, xform );
1107 
1108  if ( mLabelSearchTree )
1109  {
1110  mLabelSearchTree->insertLabel( *it, QString( palGeometry->strId() ).toInt(), ( *it )->getLayerName() );
1111  }
1112  }
1113 
1114  QgsDebugMsg( QString( "LABELING draw: %1 ms" ).arg( t.elapsed() ) );
1115 
1116  delete problem;
1117  delete labels;
1118 
1119  // delete all allocated geometries for features
1120  QHash<QgsVectorLayer*, QgsPalLayerSettings>::iterator lit;
1121  for ( lit = mActiveLayers.begin(); lit != mActiveLayers.end(); ++lit )
1122  {
1123  QgsPalLayerSettings& lyr = lit.value();
1124  for ( QList<QgsPalGeometry*>::iterator git = lyr.geometries.begin(); git != lyr.geometries.end(); ++git )
1125  delete *git;
1126  lyr.geometries.clear();
1127  }
1128 
1129  //delete all allocated geometries for diagrams
1130  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator dIt = mActiveDiagramLayers.begin();
1131  for ( ; dIt != mActiveDiagramLayers.end(); ++dIt )
1132  {
1133  QgsDiagramLayerSettings& dls = dIt.value();
1134  for ( QList<QgsPalGeometry*>::iterator git = dls.geometries.begin(); git != dls.geometries.end(); ++git )
1135  {
1136  delete *git;
1137  }
1138  dls.geometries.clear();
1139  }
1140 }
1141 
1142 QList<QgsLabelPosition> QgsPalLabeling::labelsAtPosition( const QgsPoint& p )
1143 {
1144  QList<QgsLabelPosition> positions;
1145 
1146  QList<QgsLabelPosition*> positionPointers;
1147  if ( mLabelSearchTree )
1148  {
1149  mLabelSearchTree->label( p, positionPointers );
1150  QList<QgsLabelPosition*>::const_iterator pointerIt = positionPointers.constBegin();
1151  for ( ; pointerIt != positionPointers.constEnd(); ++pointerIt )
1152  {
1153  positions.push_back( QgsLabelPosition( **pointerIt ) );
1154  }
1155  }
1156 
1157  return positions;
1158 }
1159 
1160 void QgsPalLabeling::numCandidatePositions( int& candPoint, int& candLine, int& candPolygon )
1161 {
1162  candPoint = mCandPoint;
1163  candLine = mCandLine;
1164  candPolygon = mCandPolygon;
1165 }
1166 
1167 void QgsPalLabeling::setNumCandidatePositions( int candPoint, int candLine, int candPolygon )
1168 {
1169  mCandPoint = candPoint;
1170  mCandLine = candLine;
1171  mCandPolygon = candPolygon;
1172 }
1173 
1175 {
1176  mSearch = s;
1177 }
1178 
1180 {
1181  return mSearch;
1182 }
1183 
1184 void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition* lp, QPainter* painter, const QgsMapToPixel* xform )
1185 {
1186  QgsPoint outPt = xform->transform( lp->getX(), lp->getY() );
1187  QgsPoint outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
1188 
1189  painter->save();
1190  painter->translate( QPointF( outPt.x(), outPt.y() ) );
1191  painter->rotate( -lp->getAlpha() * 180 / M_PI );
1192  QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
1193  painter->drawRect( rect );
1194  painter->restore();
1195 
1196  // save the rect
1197  rect.moveTo( outPt.x(), outPt.y() );
1198  mCandidates.append( QgsLabelCandidate( rect, lp->getCost() * 1000 ) );
1199 
1200  // show all parts of the multipart label
1201  if ( lp->getNextPart() )
1202  drawLabelCandidateRect( lp->getNextPart(), painter, xform );
1203 }
1204 
1205 void QgsPalLabeling::drawLabel( pal::LabelPosition* label, QPainter* painter, const QFont& f, const QColor& c, const QgsMapToPixel* xform, double bufferSize,
1206  const QColor& bufferColor, bool drawBuffer )
1207 {
1208  QgsPoint outPt = xform->transform( label->getX(), label->getY() );
1209 
1210  // TODO: optimize access :)
1211  const QgsPalLayerSettings& lyr = layer( QString::fromUtf8( label->getLayerName() ) );
1212 
1213  QString text = (( QgsPalGeometry* )label->getFeaturePart()->getUserGeometry() )->text();
1214  QString txt = ( label->getPartId() == -1 ? text : QString( text[label->getPartId()] ) );
1215 
1216  //add the direction symbol if needed
1217  if ( !txt.isEmpty() && lyr.placement == QgsPalLayerSettings::Line &&
1218  lyr.addDirectionSymbol && !lyr.multiLineLabels )
1219  {
1220  if ( label->getReversed() )
1221  {
1222  txt.prepend( "<" );
1223  }
1224  else
1225  {
1226  txt.append( ">" );
1227  }
1228  }
1229 
1230  //QgsDebugMsg( "drawLabel " + QString::number( drawBuffer ) + " " + txt );
1231 
1232  QStringList multiLineList;
1233  if ( lyr.multiLineLabels )
1234  {
1235  multiLineList = txt.split( "\n" );
1236  }
1237  else
1238  {
1239  multiLineList << txt;
1240  }
1241 
1242  for ( int i = 0; i < multiLineList.size(); ++i )
1243  {
1244  painter->save();
1245  painter->translate( QPointF( outPt.x(), outPt.y() ) );
1246  painter->rotate( -label->getAlpha() * 180 / M_PI );
1247 
1248  // scale down painter: the font size has been multiplied by raster scale factor
1249  // to workaround a Qt font scaling bug with small font sizes
1250  painter->scale( 1.0 / lyr.rasterCompressFactor, 1.0 / lyr.rasterCompressFactor );
1251 
1252  double yMultiLineOffset = ( multiLineList.size() - 1 - i ) * lyr.fontMetrics->height();
1253  painter->translate( QPointF( 0, - lyr.fontMetrics->descent() - yMultiLineOffset ) );
1254 
1255  if ( drawBuffer )
1256  {
1257  // we're drawing buffer
1258  drawLabelBuffer( painter, multiLineList.at( i ), f, bufferSize * lyr.vectorScaleFactor * lyr.rasterCompressFactor , bufferColor );
1259  }
1260  else
1261  {
1262  // we're drawing real label
1263  QPainterPath path;
1264  path.addText( 0, 0, f, multiLineList.at( i ) );
1265  painter->setPen( Qt::NoPen );
1266  painter->setBrush( c );
1267  painter->drawPath( path );
1268  }
1269  painter->restore();
1270 
1271  if ( label->getNextPart() )
1272  drawLabel( label->getNextPart(), painter, f, c, xform, bufferSize, bufferColor, drawBuffer );
1273  }
1274 }
1275 
1276 
1277 void QgsPalLabeling::drawLabelBuffer( QPainter* p, QString text, const QFont& font, double size, QColor color )
1278 {
1279  QPainterPath path;
1280  path.addText( 0, 0, font, text );
1281  QPen pen( color );
1282  pen.setWidthF( size );
1283  p->setPen( pen );
1284  p->setBrush( color );
1285  p->drawPath( path );
1286 }
1287 
1289 {
1290  QgsPalLabeling* lbl = new QgsPalLabeling();
1293  return lbl;
1294 }