QGIS API Documentation  2.14.11-Essen
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
feature.cpp
Go to the documentation of this file.
1 /*
2  * libpal - Automated Placement of Labels Library
3  *
4  * Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5  * University of Applied Sciences, Western Switzerland
6  * http://www.hes-so.ch
7  *
8  * Contact:
9  * maxence.laurent <at> heig-vd <dot> ch
10  * or
11  * eric.taillard <at> heig-vd <dot> ch
12  *
13  * This file is part of libpal.
14  *
15  * libpal is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation, either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * libpal is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with libpal. If not, see <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 #include "qgsgeometry.h"
31 #include "pal.h"
32 #include "layer.h"
33 #include "feature.h"
34 #include "geomfunction.h"
35 #include "labelposition.h"
36 #include "pointset.h"
37 #include "util.h"
38 #include "qgis.h"
39 #include "qgsgeos.h"
40 #include "qgsmessagelog.h"
41 #include "costcalculator.h"
42 #include <QLinkedList>
43 #include <cmath>
44 #include <cfloat>
45 
46 #ifndef M_PI
47 #define M_PI 3.14159265358979323846
48 #endif
49 
50 using namespace pal;
51 
52 FeaturePart::FeaturePart( QgsLabelFeature* feat, const GEOSGeometry* geom )
53  : mLF( feat )
54 {
55  // we'll remove const, but we won't modify that geometry
56  mGeos = const_cast<GEOSGeometry*>( geom );
57  mOwnsGeom = false; // geometry is owned by Feature class
58 
59  extractCoords( geom );
60 
61  holeOf = nullptr;
62  for ( int i = 0; i < mHoles.count(); i++ )
63  {
64  mHoles.at( i )->holeOf = this;
65  }
66 
67 }
68 
70  : PointSet( other )
71  , mLF( other.mLF )
72 {
73  Q_FOREACH ( const FeaturePart* hole, other.mHoles )
74  {
75  mHoles << new FeaturePart( *hole );
76  mHoles.last()->holeOf = this;
77  }
78 }
79 
81 {
82  // X and Y are deleted in PointSet
83 
84  qDeleteAll( mHoles );
85  mHoles.clear();
86 }
87 
88 void FeaturePart::extractCoords( const GEOSGeometry* geom )
89 {
90  const GEOSCoordSequence *coordSeq;
91  GEOSContextHandle_t geosctxt = geosContext();
92 
93  type = GEOSGeomTypeId_r( geosctxt, geom );
94 
95  if ( type == GEOS_POLYGON )
96  {
97  if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
98  {
99  int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
100 
101  for ( int i = 0; i < numHoles; ++i )
102  {
103  const GEOSGeometry* interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
104  FeaturePart* hole = new FeaturePart( mLF, interior );
105  hole->holeOf = nullptr;
106  // possibly not needed. it's not done for the exterior ring, so I'm not sure
107  // why it's just done here...
108  GeomFunction::reorderPolygon( hole->nbPoints, hole->x, hole->y );
109 
110  mHoles << hole;
111  }
112  }
113 
114  // use exterior ring for the extraction of coordinates that follows
115  geom = GEOSGetExteriorRing_r( geosctxt, geom );
116  }
117  else
118  {
119  qDeleteAll( mHoles );
120  mHoles.clear();
121  }
122 
123  // find out number of points
124  nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
125  coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
126 
127  // initialize bounding box
128  xmin = ymin = DBL_MAX;
129  xmax = ymax = -DBL_MAX;
130 
131  // initialize coordinate arrays
132  deleteCoords();
133  x = new double[nbPoints];
134  y = new double[nbPoints];
135 
136  for ( int i = 0; i < nbPoints; ++i )
137  {
138  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] );
139  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] );
140 
141  xmax = x[i] > xmax ? x[i] : xmax;
142  xmin = x[i] < xmin ? x[i] : xmin;
143 
144  ymax = y[i] > ymax ? y[i] : ymax;
145  ymin = y[i] < ymin ? y[i] : ymin;
146  }
147 }
148 
150 {
151  return mLF->layer();
152 }
153 
155 {
156  return mLF->id();
157 }
158 
160 {
161  if ( !part )
162  return false;
163 
164  if ( mLF->layer()->name() != part->layer()->name() )
165  return false;
166 
167  if ( mLF->id() == part->featureId() )
168  return true;
169 
170  // any part of joined features are also treated as having the same label feature
171  int connectedFeatureId = mLF->layer()->connectedFeatureId( mLF->id() );
172  if ( connectedFeatureId >= 0 && connectedFeatureId == mLF->layer()->connectedFeatureId( part->featureId() ) )
173  return true;
174 
175  return false;
176 }
177 
178 LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
179 {
180  QPointF quadOffset = mLF->quadOffset();
181  qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
182 
183  if ( quadOffsetX < 0 )
184  {
185  if ( quadOffsetY < 0 )
186  {
188  }
189  else if ( quadOffsetY > 0 )
190  {
192  }
193  else
194  {
196  }
197  }
198  else if ( quadOffsetX > 0 )
199  {
200  if ( quadOffsetY < 0 )
201  {
203  }
204  else if ( quadOffsetY > 0 )
205  {
207  }
208  else
209  {
211  }
212  }
213  else
214  {
215  if ( quadOffsetY < 0 )
216  {
218  }
219  else if ( quadOffsetY > 0 )
220  {
222  }
223  else
224  {
226  }
227  }
228 }
229 
231 {
232  int nbp = 1;
233 
234  // get from feature
235  double labelW = getLabelWidth();
236  double labelH = getLabelHeight();
237 
238  double cost = 0.0001;
239  int id = 0;
240 
241  double xdiff = -labelW / 2.0;
242  double ydiff = -labelH / 2.0;
243 
244  if ( !qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
245  {
246  xdiff += labelW / 2.0 * mLF->quadOffset().x();
247  }
248  if ( !qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
249  {
250  ydiff += labelH / 2.0 * mLF->quadOffset().y();
251  }
252 
253  if ( ! mLF->hasFixedPosition() )
254  {
255  if ( !qgsDoubleNear( angle, 0.0 ) )
256  {
257  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
258  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
259  xdiff = xd;
260  ydiff = yd;
261  }
262  }
263 
265  {
266  //if in "around point" placement mode, then we use the label distance to determine
267  //the label's offset
268  if ( qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
269  {
270  ydiff += mLF->quadOffset().y() * mLF->distLabel();
271  }
272  else if ( qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
273  {
274  xdiff += mLF->quadOffset().x() * mLF->distLabel();
275  }
276  else
277  {
278  xdiff += mLF->quadOffset().x() * M_SQRT1_2 * mLF->distLabel();
279  ydiff += mLF->quadOffset().y() * M_SQRT1_2 * mLF->distLabel();
280  }
281  }
282  else
283  {
284  if ( !qgsDoubleNear( mLF->positionOffset().x(), 0.0 ) )
285  {
286  xdiff += mLF->positionOffset().x();
287  }
288  if ( !qgsDoubleNear( mLF->positionOffset().y(), 0.0 ) )
289  {
290  ydiff += mLF->positionOffset().y();
291  }
292  }
293 
294  double lx = x + xdiff;
295  double ly = y + ydiff;
296 
297  if ( mLF->permissibleZonePrepared() )
298  {
299  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
300  {
301  return 0;
302  }
303  }
304 
305  lPos << new LabelPosition( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() );
306  return nbp;
307 }
308 
310 {
312  double labelWidth = getLabelWidth();
313  double labelHeight = getLabelHeight();
314  double distanceToLabel = getLabelDistance();
315  const QgsLabelFeature::VisualMargin& visualMargin = mLF->visualMargin();
316 
317  double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
318  double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );
319 
320  double cost = 0.0001;
321  int i = 0;
322  Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
323  {
324  double alpha = 0.0;
325  double deltaX = 0;
326  double deltaY = 0;
328  switch ( position )
329  {
332  alpha = 3 * M_PI_4;
333  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
334  deltaY = -visualMargin.bottom + symbolHeightOffset;
335  break;
336 
338  quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
339  alpha = M_PI_2;
340  deltaX = -labelWidth / 4.0 - visualMargin.left;
341  deltaY = -visualMargin.bottom + symbolHeightOffset;
342  break;
343 
345  quadrant = LabelPosition::QuadrantAbove;
346  alpha = M_PI_2;
347  deltaX = -labelWidth / 2.0;
348  deltaY = -visualMargin.bottom + symbolHeightOffset;
349  break;
350 
352  quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
353  alpha = M_PI_2;
354  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
355  deltaY = -visualMargin.bottom + symbolHeightOffset;
356  break;
357 
360  alpha = M_PI_4;
361  deltaX = - visualMargin.left + symbolWidthOffset;
362  deltaY = -visualMargin.bottom + symbolHeightOffset;
363  break;
364 
366  quadrant = LabelPosition::QuadrantLeft;
367  alpha = M_PI;
368  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
369  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
370  break;
371 
373  quadrant = LabelPosition::QuadrantRight;
374  alpha = 0.0;
375  deltaX = -visualMargin.left + symbolWidthOffset;
376  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
377  break;
378 
381  alpha = 5 * M_PI_4;
382  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
383  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
384  break;
385 
387  quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
388  alpha = 3 * M_PI_2;
389  deltaX = -labelWidth / 4.0 - visualMargin.left;
390  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
391  break;
392 
394  quadrant = LabelPosition::QuadrantBelow;
395  alpha = 3 * M_PI_2;
396  deltaX = -labelWidth / 2.0;
397  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
398  break;
399 
401  quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
402  alpha = 3 * M_PI_2;
403  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
404  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
405  break;
406 
409  alpha = 7 * M_PI_4;
410  deltaX = -visualMargin.left + symbolWidthOffset;
411  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
412  break;
413  }
414 
415  //have bearing, distance - calculate reference point
416  double referenceX = cos( alpha ) * distanceToLabel + x;
417  double referenceY = sin( alpha ) * distanceToLabel + y;
418 
419  double labelX = referenceX + deltaX;
420  double labelY = referenceY + deltaY;
421 
422  if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
423  {
424  lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
425  //TODO - tweak
426  cost += 0.001;
427  }
428 
429  ++i;
430  }
431 
432  return lPos.count();
433 }
434 
436 {
437  double labelWidth = getLabelWidth();
438  double labelHeight = getLabelHeight();
439  double distanceToLabel = getLabelDistance();
440 
441  int numberCandidates = mLF->layer()->pal->point_p;
442 
443  int icost = 0;
444  int inc = 2;
445 
446  double candidateAngleIncrement = 2 * M_PI / numberCandidates; /* angle bw 2 pos */
447 
448  /* various angles */
449  double a90 = M_PI / 2;
450  double a180 = M_PI;
451  double a270 = a180 + a90;
452  double a360 = 2 * M_PI;
453 
454  double gamma1, gamma2;
455 
456  if ( distanceToLabel > 0 )
457  {
458  gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
459  gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
460  }
461  else
462  {
463  gamma1 = gamma2 = a90 / 3.0;
464  }
465 
466  if ( gamma1 > a90 / 3.0 )
467  gamma1 = a90 / 3.0;
468 
469  if ( gamma2 > a90 / 3.0 )
470  gamma2 = a90 / 3.0;
471 
472  QList< LabelPosition* > candidates;
473 
474  int i;
475  double angleToCandidate;
476  for ( i = 0, angleToCandidate = M_PI / 4; i < numberCandidates; i++, angleToCandidate += candidateAngleIncrement )
477  {
478  double labelX = x;
479  double labelY = y;
480 
481  if ( angleToCandidate > a360 )
482  angleToCandidate -= a360;
483 
485 
486  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
487  {
488  labelX += distanceToLabel;
489  double iota = ( angleToCandidate + gamma1 );
490  if ( iota > a360 - gamma1 )
491  iota -= a360;
492 
493  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
494  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
495 
496  quadrant = LabelPosition::QuadrantRight;
497  }
498  else if ( angleToCandidate < a90 - gamma2 ) // top-right
499  {
500  labelX += distanceToLabel * cos( angleToCandidate );
501  labelY += distanceToLabel * sin( angleToCandidate );
503  }
504  else if ( angleToCandidate < a90 + gamma2 ) // top
505  {
506  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
507  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
508  labelY += distanceToLabel;
509  quadrant = LabelPosition::QuadrantAbove;
510  }
511  else if ( angleToCandidate < a180 - gamma1 ) // top left
512  {
513  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
514  labelY += distanceToLabel * sin( angleToCandidate );
516  }
517  else if ( angleToCandidate < a180 + gamma1 ) // left
518  {
519  labelX += -distanceToLabel - labelWidth;
520  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
521  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
522  quadrant = LabelPosition::QuadrantLeft;
523  }
524  else if ( angleToCandidate < a270 - gamma2 ) // down - left
525  {
526  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
527  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
529  }
530  else if ( angleToCandidate < a270 + gamma2 ) // down
531  {
532  labelY += -distanceToLabel - labelHeight;
533  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
534  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
535  quadrant = LabelPosition::QuadrantBelow;
536  }
537  else if ( angleToCandidate < a360 ) // down - right
538  {
539  labelX += distanceToLabel * cos( angleToCandidate );
540  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
542  }
543 
544  double cost;
545 
546  if ( numberCandidates == 1 )
547  cost = 0.0001;
548  else
549  cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
550 
551 
552  if ( mLF->permissibleZonePrepared() )
553  {
554  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
555  {
556  continue;
557  }
558  }
559 
560  candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
561 
562  icost += inc;
563 
564  if ( icost == numberCandidates )
565  {
566  icost = numberCandidates - 1;
567  inc = -2;
568  }
569  else if ( icost > numberCandidates )
570  {
571  icost = numberCandidates - 2;
572  inc = -2;
573  }
574 
575  }
576 
577  if ( !candidates.isEmpty() )
578  {
579  for ( int i = 0; i < candidates.count(); ++i )
580  {
581  lPos << candidates.at( i );
582  }
583  }
584 
585  return candidates.count();
586 }
587 
588 // TODO work with squared distance by removing call to sqrt or dist_euc2d
590 {
591  int i;
592  double distlabel = getLabelDistance();
593 
594  double xrm = getLabelWidth();
595  double yrm = getLabelHeight();
596 
597  double *d; // segments lengths distance bw pt[i] && pt[i+1]
598  double *ad; // absolute distance bw pt[0] and pt[i] along the line
599  double ll; // line length
600  double dist;
601  double bx, by, ex, ey;
602  int nbls;
603  double alpha;
604  double cost;
605 
606  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
607  if ( flags == 0 )
608  flags = FLAG_ON_LINE; // default flag
609 
610  QLinkedList<LabelPosition*> positions;
611 
612  int nbPoints;
613  double *x;
614  double *y;
615 
616  PointSet * line = mapShape;
617  nbPoints = line->nbPoints;
618  x = line->x;
619  y = line->y;
620 
621  d = new double[nbPoints-1];
622  ad = new double[nbPoints];
623 
624  ll = 0.0; // line length
625  for ( i = 0; i < line->nbPoints - 1; i++ )
626  {
627  if ( i == 0 )
628  ad[i] = 0;
629  else
630  ad[i] = ad[i-1] + d[i-1];
631 
632  d[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
633  ll += d[i];
634  }
635 
636  ad[line->nbPoints-1] = ll;
637 
638  nbls = static_cast< int >( ll / xrm ); // ratio bw line length and label width
639  dist = ( ll - xrm );
640  double l;
641 
642  if ( nbls > 0 )
643  {
644  //dist /= nbls;
645  l = 0;
646  dist = qMin( yrm, xrm );
647  }
648  else // line length < label with => centering label position
649  {
650  l = - ( xrm - ll ) / 2.0;
651  dist = xrm;
652  ll = xrm;
653  }
654 
655  double birdfly;
656  double beta;
657  i = 0;
658  while ( l < ll - xrm )
659  {
660  // => bx, by
661  line->getPointByDistance( d, ad, l, &bx, &by );
662  // same but l = l+xrm
663  line->getPointByDistance( d, ad, l + xrm, &ex, &ey );
664 
665  // Label is bigger than line ...
666  if ( l < 0 )
667  birdfly = sqrt(( x[nbPoints-1] - x[0] ) * ( x[nbPoints-1] - x[0] )
668  + ( y[nbPoints-1] - y[0] ) * ( y[nbPoints-1] - y[0] ) );
669  else
670  birdfly = sqrt(( ex - bx ) * ( ex - bx ) + ( ey - by ) * ( ey - by ) );
671 
672  cost = birdfly / xrm;
673  if ( cost > 0.98 )
674  cost = 0.0001;
675  else
676  cost = ( 1 - cost ) / 100; // < 0.0001, 0.01 > (but 0.005 is already pretty much)
677 
678  // penalize positions which are further from the line's midpoint
679  double costCenter = qAbs( ll / 2 - ( l + xrm / 2 ) ) / ll; // <0, 0.5>
680  cost += costCenter / 1000; // < 0, 0.0005 >
681 
682  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
683  {
684  alpha = 0.0;
685  }
686  else
687  alpha = atan2( ey - by, ex - bx );
688 
689  beta = alpha + M_PI / 2;
690 
692  {
693  // find out whether the line direction for this candidate is from right to left
694  bool isRightToLeft = ( alpha > M_PI / 2 || alpha <= -M_PI / 2 );
695  // meaning of above/below may be reversed if using line position dependent orientation
696  // and the line has right-to-left direction
697  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
698  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
699  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
700 
701  if ( aboveLine )
702  {
703  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
704  positions.append( new LabelPosition( i, bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
705  }
706  if ( belowLine )
707  {
708  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
709  positions.append( new LabelPosition( i, bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
710  }
711  if ( flags & FLAG_ON_LINE )
712  {
713  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
714  positions.append( new LabelPosition( i, bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
715  }
716  }
718  {
719  positions.append( new LabelPosition( i, bx - xrm / 2, by - yrm / 2, xrm, yrm, 0, cost, this ) ); // Line
720  }
721  else
722  {
723  // an invalid arrangement?
724  }
725 
726  l += dist;
727 
728  i++;
729 
730  if ( nbls == 0 )
731  break;
732  }
733 
734  //delete line;
735 
736  delete[] d;
737  delete[] ad;
738 
739  int nbp = positions.size();
740  while ( !positions.isEmpty() )
741  {
742  lPos << positions.takeFirst();
743  }
744 
745  return nbp;
746 }
747 
748 
749 LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
750 {
751  // Check that the given distance is on the given index and find the correct index and distance if not
752  while ( distance < 0 && index > 1 )
753  {
754  index--;
755  distance += path_distances[index];
756  }
757 
758  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
759  {
760  return nullptr;
761  }
762 
763  // Same thing, checking if we go off the end
764  while ( index < path_positions->nbPoints && distance > path_distances[index] )
765  {
766  distance -= path_distances[index];
767  index += 1;
768  }
769  if ( index >= path_positions->nbPoints )
770  {
771  return nullptr;
772  }
773 
774  LabelInfo* li = mLF->curvedLabelInfo();
775 
776  // Keep track of the initial index,distance incase we need to re-call get_placement_offset
777  int initial_index = index;
778  double initial_distance = distance;
779 
780  double string_height = li->label_height;
781  double old_x = path_positions->x[index-1];
782  double old_y = path_positions->y[index-1];
783 
784  double new_x = path_positions->x[index];
785  double new_y = path_positions->y[index];
786 
787  double dx = new_x - old_x;
788  double dy = new_y - old_y;
789 
790  double segment_length = path_distances[index];
791  if ( qgsDoubleNear( segment_length, 0.0 ) )
792  {
793  // Not allowed to place across on 0 length segments or discontinuities
794  return nullptr;
795  }
796 
797  LabelPosition* slp = nullptr;
798  LabelPosition* slp_tmp = nullptr;
799  // current_placement = placement_result()
800  double angle = atan2( -dy, dx );
801 
802  bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
803  if ( !orientation_forced )
804  orientation = ( angle > 0.55 * M_PI || angle < -0.45 * M_PI ? -1 : 1 );
805 
806  int upside_down_char_count = 0; // Count of characters that are placed upside down.
807 
808  for ( int i = 0; i < li->char_num; i++ )
809  {
810  double last_character_angle = angle;
811 
812  // grab the next character according to the orientation
813  LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
814 
815  // Coordinates this character will start at
816  if ( qgsDoubleNear( segment_length, 0.0 ) )
817  {
818  // Not allowed to place across on 0 length segments or discontinuities
819  delete slp;
820  return nullptr;
821  }
822 
823  double start_x = old_x + dx * distance / segment_length;
824  double start_y = old_y + dy * distance / segment_length;
825  // Coordinates this character ends at, calculated below
826  double end_x = 0;
827  double end_y = 0;
828 
829  if ( segment_length - distance >= ci.width )
830  {
831  // if the distance remaining in this segment is enough, we just go further along the segment
832  distance += ci.width;
833  end_x = old_x + dx * distance / segment_length;
834  end_y = old_y + dy * distance / segment_length;
835  }
836  else
837  {
838  // If there isn't enough distance left on this segment
839  // then we need to search until we find the line segment that ends further than ci.width away
840  do
841  {
842  old_x = new_x;
843  old_y = new_y;
844  index++;
845  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
846  {
847  delete slp;
848  return nullptr;
849  }
850  new_x = path_positions->x[index];
851  new_y = path_positions->y[index];
852  dx = new_x - old_x;
853  dy = new_y - old_y;
854  segment_length = path_distances[index];
855  }
856  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_
857 
858  // Calculate the position to place the end of the character on
859  GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );
860 
861  // Need to calculate distance on the new segment
862  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
863  }
864 
865  // Calculate angle from the start of the character to the end based on start_/end_ position
866  angle = atan2( start_y - end_y, end_x - start_x );
867  //angle = atan2(end_y-start_y, end_x-start_x);
868 
869  // Test last_character_angle vs angle
870  // since our rendering angle has changed then check against our
871  // max allowable angle change.
872  double angle_delta = last_character_angle - angle;
873  // normalise between -180 and 180
874  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
875  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
876  if (( li->max_char_angle_inside > 0 && angle_delta > 0
877  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
878  || ( li->max_char_angle_outside < 0 && angle_delta < 0
879  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
880  {
881  delete slp;
882  return nullptr;
883  }
884 
885  // Shift the character downwards since the draw position is specified at the baseline
886  // and we're calculating the mean line here
887  double dist = 0.9 * li->label_height / 2;
888  if ( orientation < 0 )
889  dist = -dist;
890  start_x += dist * cos( angle + M_PI_2 );
891  start_y -= dist * sin( angle + M_PI_2 );
892 
893  double render_angle = angle;
894 
895  double render_x = start_x;
896  double render_y = start_y;
897 
898  // Center the text on the line
899  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
900  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
901 
902  if ( orientation < 0 )
903  {
904  // rotate in place
905  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
906  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
907  render_angle += M_PI;
908  }
909 
910  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
911  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
912  if ( !slp )
913  slp = tmp;
914  else
915  slp_tmp->setNextPart( tmp );
916  slp_tmp = tmp;
917 
918  //current_placement.add_node(ci.character,render_x, -render_y, render_angle);
919  //current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
920 
921  // Normalise to 0 <= angle < 2PI
922  while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
923  while ( render_angle < 0 ) render_angle += 2 * M_PI;
924 
925  if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
926  upside_down_char_count++;
927  }
928  // END FOR
929 
930  // If we placed too many characters upside down
931  if ( upside_down_char_count >= li->char_num / 2.0 )
932  {
933  // if we auto-detected the orientation then retry with the opposite orientation
934  if ( !orientation_forced )
935  {
936  orientation = -orientation;
937  delete slp;
938  slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
939  }
940  else
941  {
942  // Otherwise we have failed to find a placement
943  delete slp;
944  return nullptr;
945  }
946  }
947 
948  return slp;
949 }
950 
951 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
952 {
953  LabelPosition* newLp = new LabelPosition( *lp );
954  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
955  return newLp;
956 }
957 
959 {
960  LabelInfo* li = mLF->curvedLabelInfo();
961 
962  // label info must be present
963  if ( !li || li->char_num == 0 )
964  return 0;
965 
966  // distance calculation
967  double* path_distances = new double[mapShape->nbPoints];
968  double total_distance = 0;
969  double old_x = -1.0, old_y = -1.0;
970  for ( int i = 0; i < mapShape->nbPoints; i++ )
971  {
972  if ( i == 0 )
973  path_distances[i] = 0;
974  else
975  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
976  old_x = mapShape->x[i];
977  old_y = mapShape->y[i];
978 
979  total_distance += path_distances[i];
980  }
981 
982  if ( qgsDoubleNear( total_distance, 0.0 ) )
983  {
984  delete[] path_distances;
985  return 0;
986  }
987 
988  //calculate overall angle of line
989  double lineAngle;
990  double bx = mapShape->x[0];
991  double by = mapShape->y[0];
992  double ex = mapShape->x[ mapShape->nbPoints - 1 ];
993  double ey = mapShape->y[ mapShape->nbPoints - 1 ];
994  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
995  {
996  lineAngle = 0.0;
997  }
998  else
999  lineAngle = atan2( ey - by, ex - bx );
1000 
1001  // find out whether the line direction for this candidate is from right to left
1002  bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
1003 
1004  QLinkedList<LabelPosition*> positions;
1005  double delta = qMax( li->label_height, total_distance / 10.0 );
1006 
1007  unsigned long flags = mLF->layer()->arrangementFlags();
1008  if ( flags == 0 )
1009  flags = FLAG_ON_LINE; // default flag
1010  // placements may need to be reversed if using line position dependent orientation
1011  // and the line has right-to-left direction
1012  bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1013 
1014  // generate curved labels
1015  for ( int i = 0; i*delta < total_distance; i++ )
1016  {
1017  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, 0, 1, i * delta );
1018 
1019  if ( slp )
1020  {
1021  // evaluate cost
1022  double angle_diff = 0.0, angle_last = 0.0, diff;
1023  LabelPosition* tmp = slp;
1024  double sin_avg = 0, cos_avg = 0;
1025  while ( tmp )
1026  {
1027  if ( tmp != slp ) // not first?
1028  {
1029  diff = fabs( tmp->getAlpha() - angle_last );
1030  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1031  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1032  angle_diff += diff;
1033  }
1034 
1035  sin_avg += sin( tmp->getAlpha() );
1036  cos_avg += cos( tmp->getAlpha() );
1037  angle_last = tmp->getAlpha();
1038  tmp = tmp->getNextPart();
1039  }
1040 
1041  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1042  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1043  if ( cost < 0.0001 ) cost = 0.0001;
1044 
1045  // penalize positions which are further from the line's midpoint
1046  double labelCenter = ( i * delta ) + getLabelWidth() / 2;
1047  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1048  cost += costCenter / 1000; // < 0, 0.0005 >
1049  slp->setCost( cost );
1050 
1051  // average angle is calculated with respect to periodicity of angles
1052  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1053  // displacement - we loop through 3 times, generating above, online then below line placements successively
1054  for ( int i = 0; i <= 2; ++i )
1055  {
1056  LabelPosition* p = nullptr;
1057  if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1058  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1059  if ( i == 1 && flags & FLAG_ON_LINE )
1060  p = _createCurvedCandidate( slp, angle_avg, 0 );
1061  if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1062  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1063 
1064  if ( p && mLF->permissibleZonePrepared() )
1065  {
1066  bool within = true;
1067  LabelPosition* currentPos = p;
1068  while ( within && currentPos )
1069  {
1070  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1071  currentPos = currentPos->getNextPart();
1072  }
1073  if ( !within )
1074  {
1075  delete p;
1076  p = nullptr;
1077  }
1078  }
1079 
1080  if ( p )
1081  positions.append( p );
1082  }
1083  // delete original candidate
1084  delete slp;
1085  }
1086  }
1087 
1088 
1089  int nbp = positions.size();
1090  for ( int i = 0; i < nbp; i++ )
1091  {
1092  lPos << positions.takeFirst();
1093  }
1094 
1095  delete[] path_distances;
1096 
1097  return nbp;
1098 }
1099 
1100 
1101 
1102 
1103 /*
1104  * seg 2
1105  * pt3 ____________pt2
1106  * ¦ ¦
1107  * ¦ ¦
1108  * seg 3 ¦ BBOX ¦ seg 1
1109  * ¦ ¦
1110  * ¦____________¦
1111  * pt0 seg 0 pt1
1112  *
1113  */
1114 
1116 {
1117  int i;
1118  int j;
1119 
1120  double labelWidth = getLabelWidth();
1121  double labelHeight = getLabelHeight();
1122 
1123  QLinkedList<PointSet*> shapes_toProcess;
1124  QLinkedList<PointSet*> shapes_final;
1125 
1126  mapShape->parent = nullptr;
1127 
1128  shapes_toProcess.append( mapShape );
1129 
1130  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1131 
1132  int nbp;
1133 
1134  if ( !shapes_final.isEmpty() )
1135  {
1136  QLinkedList<LabelPosition*> positions;
1137 
1138  int id = 0; // ids for candidates
1139  double dlx, dly; // delta from label center and bottom-left corner
1140  double alpha = 0.0; // rotation for the label
1141  double px, py;
1142  double dx;
1143  double dy;
1144  int bbid;
1145  double beta;
1146  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1147  double rx, ry;
1148  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1149  j = 0;
1150 
1151  // Compute bounding box foreach finalShape
1152  while ( !shapes_final.isEmpty() )
1153  {
1154  PointSet *shape = shapes_final.takeFirst();
1155  boxes[j] = shape->compute_chull_bbox();
1156 
1157  if ( shape->parent )
1158  delete shape;
1159 
1160  j++;
1161  }
1162 
1163  //dx = dy = min( yrm, xrm ) / 2;
1164  dx = labelWidth / 2.0;
1165  dy = labelHeight / 2.0;
1166 
1167 
1168  int numTry = 0;
1169 
1170  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1171  //then use a smaller limit for number of iterations
1172  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1173 
1174  do
1175  {
1176  for ( bbid = 0; bbid < j; bbid++ )
1177  {
1178  CHullBox *box = boxes[bbid];
1179 
1180  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1181  {
1182  // Very Large BBOX (should never occur)
1183  continue;
1184  }
1185 
1187  {
1188  //check width/height of bbox is sufficient for label
1189  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1190  mLF->permissibleZone().boundingBox().height() < labelHeight )
1191  {
1192  //no way label can fit in this box, skip it
1193  continue;
1194  }
1195  }
1196 
1197  bool enoughPlace = false;
1199  {
1200  enoughPlace = true;
1201  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1202  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1203  int i, j;
1204 
1205  // Virtual label: center on bbox center, label size = 2x original size
1206  // alpha = 0.
1207  // If all corner are in bbox then place candidates horizontaly
1208  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1209  {
1210  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1211  {
1212  if ( !mapShape->containsPoint( rx, ry ) )
1213  {
1214  enoughPlace = false;
1215  break;
1216  }
1217  }
1218  if ( !enoughPlace )
1219  {
1220  break;
1221  }
1222  }
1223 
1224  } // arrangement== FREE ?
1225 
1226  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1227  {
1228  alpha = 0.0; // HORIZ
1229  }
1230  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1231  {
1232  if ( box->alpha <= M_PI / 4 )
1233  {
1234  alpha = box->alpha;
1235  }
1236  else
1237  {
1238  alpha = box->alpha - M_PI / 2;
1239  }
1240  }
1241  else if ( box->length > box->width )
1242  {
1243  alpha = box->alpha - M_PI / 2;
1244  }
1245  else
1246  {
1247  alpha = box->alpha;
1248  }
1249 
1250  beta = atan2( labelHeight, labelWidth ) + alpha;
1251 
1252 
1253  //alpha = box->alpha;
1254 
1255  // delta from label center and down-left corner
1256  dlx = cos( beta ) * diago;
1257  dly = sin( beta ) * diago;
1258 
1259  double px0, py0;
1260 
1261  px0 = box->width / 2.0;
1262  py0 = box->length / 2.0;
1263 
1264  px0 -= ceil( px0 / dx ) * dx;
1265  py0 -= ceil( py0 / dy ) * dy;
1266 
1267  for ( px = px0; px <= box->width; px += dx )
1268  {
1269  for ( py = py0; py <= box->length; py += dy )
1270  {
1271 
1272  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1273  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1274 
1275  rx += box->x[0];
1276  ry += box->y[0];
1277 
1278  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1279  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1280  : mapShape->containsPoint( rx, ry ) );
1281  if ( candidateAcceptable )
1282  {
1283  // cost is set to minimal value, evaluated later
1284  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1285  }
1286  }
1287  }
1288  } // forall box
1289 
1290  nbp = positions.size();
1291  if ( nbp == 0 )
1292  {
1293  dx /= 2;
1294  dy /= 2;
1295  numTry++;
1296  }
1297  }
1298  while ( nbp == 0 && numTry < maxTry );
1299 
1300  nbp = positions.size();
1301 
1302  for ( i = 0; i < nbp; i++ )
1303  {
1304  lPos << positions.takeFirst();
1305  }
1306 
1307  for ( bbid = 0; bbid < j; bbid++ )
1308  {
1309  delete boxes[bbid];
1310  }
1311 
1312  delete[] boxes;
1313  }
1314  else
1315  {
1316  nbp = 0;
1317  }
1318 
1319  return nbp;
1320 }
1321 
1323  double bboxMin[2], double bboxMax[2],
1324  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1325 {
1326  double bbox[4];
1327 
1328  bbox[0] = bboxMin[0];
1329  bbox[1] = bboxMin[1];
1330  bbox[2] = bboxMax[0];
1331  bbox[3] = bboxMax[1];
1332 
1333  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1334 
1335  if ( mLF->hasFixedPosition() )
1336  {
1337  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1338  }
1339  else
1340  {
1341  switch ( type )
1342  {
1343  case GEOS_POINT:
1345  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1347  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1348  else
1349  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1350  break;
1351  case GEOS_LINESTRING:
1353  createCurvedCandidatesAlongLine( lPos, mapShape );
1354  else
1355  createCandidatesAlongLine( lPos, mapShape );
1356  break;
1357 
1358  case GEOS_POLYGON:
1359  switch ( mLF->layer()->arrangement() )
1360  {
1363  double cx, cy;
1364  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1366  createCandidatesOverPoint( cx, cy, lPos, angle );
1367  else
1368  createCandidatesAroundPoint( cx, cy, lPos, angle );
1369  break;
1371  createCandidatesAlongLine( lPos, mapShape );
1372  break;
1373  default:
1374  createCandidatesForPolygon( lPos, mapShape );
1375  break;
1376  }
1377  }
1378  }
1379 
1380  // purge candidates that are outside the bbox
1381 
1383  while ( i.hasNext() )
1384  {
1385  LabelPosition* pos = i.next();
1386  bool outside = false;
1387  if ( mLF->layer()->pal->getShowPartial() )
1388  outside = !pos->isIntersect( bbox );
1389  else
1390  outside = !pos->isInside( bbox );
1391  if ( outside )
1392  {
1393  i.remove();
1394  delete pos;
1395  }
1396  else // this one is OK
1397  {
1398  pos->insertIntoIndex( candidates );
1399  }
1400  }
1401 
1402  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1403  return lPos.count();
1404 }
1405 
1406 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1407 {
1408  if ( !mGeos )
1409  createGeosGeom();
1410 
1411  GEOSContextHandle_t ctxt = geosContext();
1412  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1413 
1414  double sizeCost = 0;
1415  if ( geomType == GEOS_LINESTRING )
1416  {
1417  double length;
1418  try
1419  {
1420  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1421  return; // failed to calculate length
1422  }
1423  catch ( GEOSException &e )
1424  {
1425  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1426  return;
1427  }
1428  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1429  if ( length >= bbox_length / 4 )
1430  return; // the line is longer than quarter of height or width - don't penalize it
1431 
1432  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1433  }
1434  else if ( geomType == GEOS_POLYGON )
1435  {
1436  double area;
1437  try
1438  {
1439  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1440  return;
1441  }
1442  catch ( GEOSException &e )
1443  {
1444  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1445  return;
1446  }
1447  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1448  if ( area >= bbox_area / 16 )
1449  return; // covers more than 1/16 of our view - don't penalize it
1450 
1451  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1452  }
1453  else
1454  return; // no size penalty for points
1455 
1456  // apply the penalty
1457  for ( int i = 0; i < nbp; i++ )
1458  {
1459  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1460  }
1461 }
1462 
1464 {
1465  if ( !p2->mGeos )
1466  p2->createGeosGeom();
1467 
1468  try
1469  {
1470  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1471  }
1472  catch ( GEOSException &e )
1473  {
1474  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1475  return false;
1476  }
1477 }
1478 
1480 {
1481  if ( !mGeos )
1482  createGeosGeom();
1483  if ( !other->mGeos )
1484  other->createGeosGeom();
1485 
1486  GEOSContextHandle_t ctxt = geosContext();
1487  try
1488  {
1489  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1490  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1491  GEOSGeometry* geoms[2] = { g1, g2 };
1492  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1493  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1494  GEOSGeom_destroy_r( ctxt, g );
1495 
1496  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1497  {
1498  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1499  GEOSGeom_destroy_r( ctxt, gTmp );
1500  return false;
1501  }
1502  invalidateGeos();
1503 
1504  // set up new geometry
1505  mGeos = gTmp;
1506  mOwnsGeom = true;
1507 
1508  deleteCoords();
1509  qDeleteAll( mHoles );
1510  mHoles.clear();
1511  extractCoords( mGeos );
1512  return true;
1513  }
1514  catch ( GEOSException &e )
1515  {
1516  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1517  return false;
1518  }
1519 }
1520 
1522 {
1523  if ( mLF->alwaysShow() )
1524  {
1525  //if feature is set to always show, bump the priority up by orders of magnitude
1526  //so that other feature's labels are unlikely to be placed over the label for this feature
1527  //(negative numbers due to how pal::extract calculates inactive cost)
1528  return -0.2;
1529  }
1530 
1531  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1532 }
Label below point, slightly right of center.
pal::Layer * layer() const
Get PAL layer of the label feature. Should be only used internally in PAL.
double length
Definition: pointset.h:56
static unsigned index
Label on bottom-left of point.
virtual ~FeaturePart()
Delete the feature.
Definition: feature.cpp:80
void invalidateGeos()
Definition: pointset.cpp:204
bool isIntersect(double *bbox)
Is the labelposition intersect the bounding-box ?
static bool candidateSortGrow(const LabelPosition *c1, const LabelPosition *c2)
Sorts label candidates in ascending order of cost.
QList< FeaturePart * > mHoles
Definition: feature.h:232
double max_char_angle_outside
Definition: feature.h:72
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
double getLabelHeight() const
Definition: feature.h:196
double getWidth() const
Label on top-left of point.
void setCost(double newCost)
Sets the candidate label position's geographical cost.
A set of features which influence the labelling process.
Definition: layer.h:55
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode...
int createCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for line feature.
Definition: feature.cpp:589
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label's permissibleZone().
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
const T & at(int i) const
int createCandidatesAroundPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generate candidates for point feature, located around a specified point.
Definition: feature.cpp:435
Candidates are placed in predefined positions around a point.
friend class LabelPosition
Definition: pointset.h:66
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
Label on top of point, slightly right of center.
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
void createGeosGeom() const
Definition: pointset.cpp:150
double getLabelWidth() const
Definition: feature.h:195
double priority() const
Returns the layer's priority, between 0 and 1.
Definition: layer.h:164
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1479
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:211
double priority() const
Returns the feature's labeling priority.
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1463
void addSizePenalty(int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1406
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:769
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Definition: qgis.h:285
double x() const
Get the x value of the point.
Definition: qgspoint.h:128
#define M_PI_2
Definition: util.cpp:43
int createCurvedCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:958
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
Arranges candidates following the curvature of a line feature.
CharacterInfo * char_info
Definition: feature.h:75
pal::LabelInfo * curvedLabelInfo() const
Get additional infor required for curved label placement. Returns null if not set.
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
QgsPalLayerSettings::Placement arrangement() const
Returns the layer's arrangement policy.
Definition: layer.h:91
double width
Definition: pointset.h:55
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:192
int count(const T &value) const
qreal x() const
qreal y() const
QgsGeometry permissibleZone() const
Returns the label's permissible zone geometry.
Label on left of point.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
bool getShowPartial()
Get flag show partial label.
Definition: pal.cpp:624
bool isEmpty() const
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py)
Get a point a set distance along a line geometry.
Definition: pointset.cpp:811
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1521
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:159
PointSet * parent
Definition: pointset.h:161
double getLabelDistance() const
Definition: feature.h:197
double * x
Definition: pointset.h:152
double ymax
Definition: pointset.h:175
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
double xmin
Definition: pointset.h:172
bool isEmpty() const
PointSet * holeOf
Definition: pointset.h:160
QgsPoint fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
static LabelPosition * _createCurvedCandidate(LabelPosition *lp, double angle, double dist)
Definition: feature.cpp:951
double ymin
Definition: pointset.h:174
double getY(int i=0) const
get the down-left y coordinate
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Arranges candidates in a circle around a point (or centroid of a polygon).
double bottom
Bottom margin.
static void logMessage(const QString &message, const QString &tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
Optional additional info about label (for curved labels)
Definition: feature.h:52
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:149
int createCandidatesOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:230
double label_height
Definition: feature.h:73
double length() const
Returns length of line geometry.
Definition: pointset.cpp:858
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:60
LabelPosition * getNextPart() const
GEOSContextHandle_t geosContext()
Get GEOS context handle to be used in all GEOS library calls with reentrant API.
Definition: pal.cpp:48
Stores visual margins for labels (left, right, top and bottom)
void deleteCoords()
Definition: pointset.cpp:232
double getX(int i=0) const
get the down-left x coordinate
Main class to handle feature.
Definition: feature.h:90
Offset distance applies from rendered symbol bounds.
void setPartId(int id)
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
iterator end()
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:268
double * y
Definition: pointset.h:153
static void splitPolygons(QLinkedList< PointSet * > &shapes_toProcess, QLinkedList< PointSet * > &shapes_final, double xrm, double yrm)
Split a concave shape into several convex shapes.
Definition: pointset.cpp:295
bool hasNext() const
double getAlpha() const
get alpha
const VisualMargin & visualMargin() const
Returns the visual margin for the label feature.
int createCandidatesForPolygon(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1115
Pal * pal
Definition: layer.h:245
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:572
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
QgsPoint positionOffset() const
Applies only to "offset from point" placement strategy.
int createCandidates(QList< LabelPosition * > &lPos, double bboxMin[2], double bboxMax[2], PointSet *mapShape, RTree< LabelPosition *, double, 2, double > *candidates)
Generic method to generate label candidates for the feature.
Definition: feature.cpp:1322
double x[4]
Definition: pointset.h:50
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
int connectedFeatureId(QgsFeatureId featureId) const
Returns the connected feature ID for a label feature ID, which is unique for all features which have ...
Definition: layer.cpp:413
double getHeight() const
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
double y[4]
Definition: pointset.h:51
double max_char_angle_inside
Definition: feature.h:71
double right
Right margin.
int createCandidatesAtOrderedPositionsOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generates candidates following a prioritised list of predefined positions around a point...
Definition: feature.cpp:309
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:88
double distLabel() const
Applies to "around point" placement strategy or linestring features.
GEOSGeometry * mGeos
Definition: pointset.h:148
LabelPosition is a candidate feature label position.
Definition: labelposition.h:50
QString name() const
Returns the layer's name.
Definition: layer.h:86
LineArrangementFlags arrangementFlags() const
Returns the layer's arrangement flags.
Definition: layer.h:102
QgsLabelFeature * mLF
Definition: feature.h:231
Label on top of point, slightly left of center.
qint64 QgsFeatureId
Definition: qgsfeature.h:31
double y() const
Get the y value of the point.
Definition: qgspoint.h:136
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:60
Label on right of point.
double alpha
Definition: pointset.h:53
static int reorderPolygon(int nbPoints, double *x, double *y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside...
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
Definition: feature.cpp:52
qreal height() const
#define M_PI
Definition: feature.cpp:47
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:207
double xmax
Definition: pointset.h:173
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int orientation, int index, double distance)
Definition: feature.cpp:749
int char_num
Definition: feature.h:74
iterator begin()
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
int size() const
qreal width() const
bool mOwnsGeom
Definition: pointset.h:149
void append(const T &value)
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:212
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:154
Arranges candidates scattered throughout a polygon feature.