QGIS API Documentation  2.14.0-Essen
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 
230 int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosition*>& lPos, double angle, PointSet *mapShape )
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 ( mapShape && type == GEOS_POLYGON && mLF->layer()->fitInPolygonOnly() )
298  {
299  if ( !mapShape->containsLabelCandidate( 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  lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
423 
424  //TODO - tweak
425  cost += 0.001;
426 
427  ++i;
428  }
429 
430  return lPos.count();
431 }
432 
433 int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPosition* >& lPos, double angle, PointSet *mapShape )
434 {
435  double labelWidth = getLabelWidth();
436  double labelHeight = getLabelHeight();
437  double distanceToLabel = getLabelDistance();
438 
439  int numberCandidates = mLF->layer()->pal->point_p;
440 
441  int icost = 0;
442  int inc = 2;
443 
444  double candidateAngleIncrement = 2 * M_PI / numberCandidates; /* angle bw 2 pos */
445 
446  /* various angles */
447  double a90 = M_PI / 2;
448  double a180 = M_PI;
449  double a270 = a180 + a90;
450  double a360 = 2 * M_PI;
451 
452  double gamma1, gamma2;
453 
454  if ( distanceToLabel > 0 )
455  {
456  gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
457  gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
458  }
459  else
460  {
461  gamma1 = gamma2 = a90 / 3.0;
462  }
463 
464  if ( gamma1 > a90 / 3.0 )
465  gamma1 = a90 / 3.0;
466 
467  if ( gamma2 > a90 / 3.0 )
468  gamma2 = a90 / 3.0;
469 
470  QList< LabelPosition* > candidates;
471 
472  int i;
473  double angleToCandidate;
474  for ( i = 0, angleToCandidate = M_PI / 4; i < numberCandidates; i++, angleToCandidate += candidateAngleIncrement )
475  {
476  double labelX = x;
477  double labelY = y;
478 
479  if ( angleToCandidate > a360 )
480  angleToCandidate -= a360;
481 
483 
484  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
485  {
486  labelX += distanceToLabel;
487  double iota = ( angleToCandidate + gamma1 );
488  if ( iota > a360 - gamma1 )
489  iota -= a360;
490 
491  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
492  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
493 
494  quadrant = LabelPosition::QuadrantRight;
495  }
496  else if ( angleToCandidate < a90 - gamma2 ) // top-right
497  {
498  labelX += distanceToLabel * cos( angleToCandidate );
499  labelY += distanceToLabel * sin( angleToCandidate );
501  }
502  else if ( angleToCandidate < a90 + gamma2 ) // top
503  {
504  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
505  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
506  labelY += distanceToLabel;
507  quadrant = LabelPosition::QuadrantAbove;
508  }
509  else if ( angleToCandidate < a180 - gamma1 ) // top left
510  {
511  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
512  labelY += distanceToLabel * sin( angleToCandidate );
514  }
515  else if ( angleToCandidate < a180 + gamma1 ) // left
516  {
517  labelX += -distanceToLabel - labelWidth;
518  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
519  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
520  quadrant = LabelPosition::QuadrantLeft;
521  }
522  else if ( angleToCandidate < a270 - gamma2 ) // down - left
523  {
524  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
525  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
527  }
528  else if ( angleToCandidate < a270 + gamma2 ) // down
529  {
530  labelY += -distanceToLabel - labelHeight;
531  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
532  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
533  quadrant = LabelPosition::QuadrantBelow;
534  }
535  else if ( angleToCandidate < a360 ) // down - right
536  {
537  labelX += distanceToLabel * cos( angleToCandidate );
538  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
540  }
541 
542  double cost;
543 
544  if ( numberCandidates == 1 )
545  cost = 0.0001;
546  else
547  cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
548 
549 
550  if ( mapShape && type == GEOS_POLYGON && mLF->layer()->fitInPolygonOnly() )
551  {
552  if ( !mapShape->containsLabelCandidate( labelX, labelY, labelWidth, labelHeight, angle ) )
553  {
554  continue;
555  }
556  }
557 
558  candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
559 
560  icost += inc;
561 
562  if ( icost == numberCandidates )
563  {
564  icost = numberCandidates - 1;
565  inc = -2;
566  }
567  else if ( icost > numberCandidates )
568  {
569  icost = numberCandidates - 2;
570  inc = -2;
571  }
572 
573  }
574 
575  if ( !candidates.isEmpty() )
576  {
577  for ( int i = 0; i < candidates.count(); ++i )
578  {
579  lPos << candidates.at( i );
580  }
581  }
582 
583  return candidates.count();
584 }
585 
586 // TODO work with squared distance by removing call to sqrt or dist_euc2d
588 {
589  int i;
590  double distlabel = getLabelDistance();
591 
592  double xrm = getLabelWidth();
593  double yrm = getLabelHeight();
594 
595  double *d; // segments lengths distance bw pt[i] && pt[i+1]
596  double *ad; // absolute distance bw pt[0] and pt[i] along the line
597  double ll; // line length
598  double dist;
599  double bx, by, ex, ey;
600  int nbls;
601  double alpha;
602  double cost;
603 
604  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
605  if ( flags == 0 )
606  flags = FLAG_ON_LINE; // default flag
607 
608  QLinkedList<LabelPosition*> positions;
609 
610  int nbPoints;
611  double *x;
612  double *y;
613 
614  PointSet * line = mapShape;
615  nbPoints = line->nbPoints;
616  x = line->x;
617  y = line->y;
618 
619  d = new double[nbPoints-1];
620  ad = new double[nbPoints];
621 
622  ll = 0.0; // line length
623  for ( i = 0; i < line->nbPoints - 1; i++ )
624  {
625  if ( i == 0 )
626  ad[i] = 0;
627  else
628  ad[i] = ad[i-1] + d[i-1];
629 
630  d[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
631  ll += d[i];
632  }
633 
634  ad[line->nbPoints-1] = ll;
635 
636  nbls = static_cast< int >( ll / xrm ); // ratio bw line length and label width
637  dist = ( ll - xrm );
638  double l;
639 
640  if ( nbls > 0 )
641  {
642  //dist /= nbls;
643  l = 0;
644  dist = qMin( yrm, xrm );
645  }
646  else // line length < label with => centering label position
647  {
648  l = - ( xrm - ll ) / 2.0;
649  dist = xrm;
650  ll = xrm;
651  }
652 
653  double birdfly;
654  double beta;
655  i = 0;
656  while ( l < ll - xrm )
657  {
658  // => bx, by
659  line->getPointByDistance( d, ad, l, &bx, &by );
660  // same but l = l+xrm
661  line->getPointByDistance( d, ad, l + xrm, &ex, &ey );
662 
663  // Label is bigger than line ...
664  if ( l < 0 )
665  birdfly = sqrt(( x[nbPoints-1] - x[0] ) * ( x[nbPoints-1] - x[0] )
666  + ( y[nbPoints-1] - y[0] ) * ( y[nbPoints-1] - y[0] ) );
667  else
668  birdfly = sqrt(( ex - bx ) * ( ex - bx ) + ( ey - by ) * ( ey - by ) );
669 
670  cost = birdfly / xrm;
671  if ( cost > 0.98 )
672  cost = 0.0001;
673  else
674  cost = ( 1 - cost ) / 100; // < 0.0001, 0.01 > (but 0.005 is already pretty much)
675 
676  // penalize positions which are further from the line's midpoint
677  double costCenter = qAbs( ll / 2 - ( l + xrm / 2 ) ) / ll; // <0, 0.5>
678  cost += costCenter / 1000; // < 0, 0.0005 >
679 
680  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
681  {
682  alpha = 0.0;
683  }
684  else
685  alpha = atan2( ey - by, ex - bx );
686 
687  beta = alpha + M_PI / 2;
688 
690  {
691  // find out whether the line direction for this candidate is from right to left
692  bool isRightToLeft = ( alpha > M_PI / 2 || alpha <= -M_PI / 2 );
693  // meaning of above/below may be reversed if using line position dependent orientation
694  // and the line has right-to-left direction
695  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
696  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
697  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
698 
699  if ( aboveLine )
700  {
701  if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
702  positions.append( new LabelPosition( i, bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
703  }
704  if ( belowLine )
705  {
706  if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
707  positions.append( new LabelPosition( i, bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
708  }
709  if ( flags & FLAG_ON_LINE )
710  {
711  if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
712  positions.append( new LabelPosition( i, bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
713  }
714  }
716  {
717  positions.append( new LabelPosition( i, bx - xrm / 2, by - yrm / 2, xrm, yrm, 0, cost, this ) ); // Line
718  }
719  else
720  {
721  // an invalid arrangement?
722  }
723 
724  l += dist;
725 
726  i++;
727 
728  if ( nbls == 0 )
729  break;
730  }
731 
732  //delete line;
733 
734  delete[] d;
735  delete[] ad;
736 
737  int nbp = positions.size();
738  while ( !positions.isEmpty() )
739  {
740  lPos << positions.takeFirst();
741  }
742 
743  return nbp;
744 }
745 
746 
747 LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
748 {
749  // Check that the given distance is on the given index and find the correct index and distance if not
750  while ( distance < 0 && index > 1 )
751  {
752  index--;
753  distance += path_distances[index];
754  }
755 
756  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
757  {
758  return nullptr;
759  }
760 
761  // Same thing, checking if we go off the end
762  while ( index < path_positions->nbPoints && distance > path_distances[index] )
763  {
764  distance -= path_distances[index];
765  index += 1;
766  }
767  if ( index >= path_positions->nbPoints )
768  {
769  return nullptr;
770  }
771 
772  LabelInfo* li = mLF->curvedLabelInfo();
773 
774  // Keep track of the initial index,distance incase we need to re-call get_placement_offset
775  int initial_index = index;
776  double initial_distance = distance;
777 
778  double string_height = li->label_height;
779  double old_x = path_positions->x[index-1];
780  double old_y = path_positions->y[index-1];
781 
782  double new_x = path_positions->x[index];
783  double new_y = path_positions->y[index];
784 
785  double dx = new_x - old_x;
786  double dy = new_y - old_y;
787 
788  double segment_length = path_distances[index];
789  if ( qgsDoubleNear( segment_length, 0.0 ) )
790  {
791  // Not allowed to place across on 0 length segments or discontinuities
792  return nullptr;
793  }
794 
795  LabelPosition* slp = nullptr;
796  LabelPosition* slp_tmp = nullptr;
797  // current_placement = placement_result()
798  double angle = atan2( -dy, dx );
799 
800  bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
801  if ( !orientation_forced )
802  orientation = ( angle > 0.55 * M_PI || angle < -0.45 * M_PI ? -1 : 1 );
803 
804  int upside_down_char_count = 0; // Count of characters that are placed upside down.
805 
806  for ( int i = 0; i < li->char_num; i++ )
807  {
808  double last_character_angle = angle;
809 
810  // grab the next character according to the orientation
811  LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
812 
813  // Coordinates this character will start at
814  if ( qgsDoubleNear( segment_length, 0.0 ) )
815  {
816  // Not allowed to place across on 0 length segments or discontinuities
817  delete slp;
818  return nullptr;
819  }
820 
821  double start_x = old_x + dx * distance / segment_length;
822  double start_y = old_y + dy * distance / segment_length;
823  // Coordinates this character ends at, calculated below
824  double end_x = 0;
825  double end_y = 0;
826 
827  if ( segment_length - distance >= ci.width )
828  {
829  // if the distance remaining in this segment is enough, we just go further along the segment
830  distance += ci.width;
831  end_x = old_x + dx * distance / segment_length;
832  end_y = old_y + dy * distance / segment_length;
833  }
834  else
835  {
836  // If there isn't enough distance left on this segment
837  // then we need to search until we find the line segment that ends further than ci.width away
838  do
839  {
840  old_x = new_x;
841  old_y = new_y;
842  index++;
843  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
844  {
845  delete slp;
846  return nullptr;
847  }
848  new_x = path_positions->x[index];
849  new_y = path_positions->y[index];
850  dx = new_x - old_x;
851  dy = new_y - old_y;
852  segment_length = path_distances[index];
853  }
854  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_
855 
856  // Calculate the position to place the end of the character on
857  GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );
858 
859  // Need to calculate distance on the new segment
860  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
861  }
862 
863  // Calculate angle from the start of the character to the end based on start_/end_ position
864  angle = atan2( start_y - end_y, end_x - start_x );
865  //angle = atan2(end_y-start_y, end_x-start_x);
866 
867  // Test last_character_angle vs angle
868  // since our rendering angle has changed then check against our
869  // max allowable angle change.
870  double angle_delta = last_character_angle - angle;
871  // normalise between -180 and 180
872  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
873  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
874  if (( li->max_char_angle_inside > 0 && angle_delta > 0
875  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
876  || ( li->max_char_angle_outside < 0 && angle_delta < 0
877  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
878  {
879  delete slp;
880  return nullptr;
881  }
882 
883  double render_angle = angle;
884 
885  double render_x = start_x;
886  double render_y = start_y;
887 
888  // Center the text on the line
889  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
890  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
891 
892  if ( orientation < 0 )
893  {
894  // rotate in place
895  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
896  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
897  render_angle += M_PI;
898  }
899 
900  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
901  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
902  if ( !slp )
903  slp = tmp;
904  else
905  slp_tmp->setNextPart( tmp );
906  slp_tmp = tmp;
907 
908  //current_placement.add_node(ci.character,render_x, -render_y, render_angle);
909  //current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
910 
911  // Normalise to 0 <= angle < 2PI
912  while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
913  while ( render_angle < 0 ) render_angle += 2 * M_PI;
914 
915  if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
916  upside_down_char_count++;
917  }
918  // END FOR
919 
920  // If we placed too many characters upside down
921  if ( upside_down_char_count >= li->char_num / 2.0 )
922  {
923  // if we auto-detected the orientation then retry with the opposite orientation
924  if ( !orientation_forced )
925  {
926  orientation = -orientation;
927  delete slp;
928  slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
929  }
930  else
931  {
932  // Otherwise we have failed to find a placement
933  delete slp;
934  return nullptr;
935  }
936  }
937 
938  return slp;
939 }
940 
941 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
942 {
943  LabelPosition* newLp = new LabelPosition( *lp );
944  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
945  return newLp;
946 }
947 
949 {
950  LabelInfo* li = mLF->curvedLabelInfo();
951 
952  // label info must be present
953  if ( !li || li->char_num == 0 )
954  return 0;
955 
956  // distance calculation
957  double* path_distances = new double[mapShape->nbPoints];
958  double total_distance = 0;
959  double old_x = -1.0, old_y = -1.0;
960  for ( int i = 0; i < mapShape->nbPoints; i++ )
961  {
962  if ( i == 0 )
963  path_distances[i] = 0;
964  else
965  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
966  old_x = mapShape->x[i];
967  old_y = mapShape->y[i];
968 
969  total_distance += path_distances[i];
970  }
971 
972  if ( qgsDoubleNear( total_distance, 0.0 ) )
973  {
974  delete[] path_distances;
975  return 0;
976  }
977 
978  //calculate overall angle of line
979  double lineAngle;
980  double bx = mapShape->x[0];
981  double by = mapShape->y[0];
982  double ex = mapShape->x[ mapShape->nbPoints - 1 ];
983  double ey = mapShape->y[ mapShape->nbPoints - 1 ];
984  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
985  {
986  lineAngle = 0.0;
987  }
988  else
989  lineAngle = atan2( ey - by, ex - bx );
990 
991  // find out whether the line direction for this candidate is from right to left
992  bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
993 
994  QLinkedList<LabelPosition*> positions;
995  double delta = qMax( li->label_height, total_distance / 10.0 );
996 
997  unsigned long flags = mLF->layer()->arrangementFlags();
998  if ( flags == 0 )
999  flags = FLAG_ON_LINE; // default flag
1000  // placements may need to be reversed if using line position dependent orientation
1001  // and the line has right-to-left direction
1002  bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1003 
1004  // generate curved labels
1005  for ( int i = 0; i*delta < total_distance; i++ )
1006  {
1007  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, 0, 1, i * delta );
1008 
1009  if ( slp )
1010  {
1011  // evaluate cost
1012  double angle_diff = 0.0, angle_last = 0.0, diff;
1013  LabelPosition* tmp = slp;
1014  double sin_avg = 0, cos_avg = 0;
1015  while ( tmp )
1016  {
1017  if ( tmp != slp ) // not first?
1018  {
1019  diff = fabs( tmp->getAlpha() - angle_last );
1020  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1021  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1022  angle_diff += diff;
1023  }
1024 
1025  sin_avg += sin( tmp->getAlpha() );
1026  cos_avg += cos( tmp->getAlpha() );
1027  angle_last = tmp->getAlpha();
1028  tmp = tmp->getNextPart();
1029  }
1030 
1031  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1032  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1033  if ( cost < 0.0001 ) cost = 0.0001;
1034 
1035  // penalize positions which are further from the line's midpoint
1036  double labelCenter = ( i * delta ) + getLabelWidth() / 2;
1037  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1038  cost += costCenter / 1000; // < 0, 0.0005 >
1039  slp->setCost( cost );
1040 
1041  // average angle is calculated with respect to periodicity of angles
1042  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1043  // displacement
1044  if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
1045  positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() ) );
1046  if ( flags & FLAG_ON_LINE )
1047  positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 ) );
1048  if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
1049  positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height - mLF->distLabel() ) );
1050 
1051  // delete original candidate
1052  delete slp;
1053  }
1054  }
1055 
1056 
1057  int nbp = positions.size();
1058  for ( int i = 0; i < nbp; i++ )
1059  {
1060  lPos << positions.takeFirst();
1061  }
1062 
1063  delete[] path_distances;
1064 
1065  return nbp;
1066 }
1067 
1068 
1069 
1070 
1071 /*
1072  * seg 2
1073  * pt3 ____________pt2
1074  * ¦ ¦
1075  * ¦ ¦
1076  * seg 3 ¦ BBOX ¦ seg 1
1077  * ¦ ¦
1078  * ¦____________¦
1079  * pt0 seg 0 pt1
1080  *
1081  */
1082 
1084 {
1085  int i;
1086  int j;
1087 
1088  double labelWidth = getLabelWidth();
1089  double labelHeight = getLabelHeight();
1090 
1091  QLinkedList<PointSet*> shapes_toProcess;
1092  QLinkedList<PointSet*> shapes_final;
1093 
1094  mapShape->parent = nullptr;
1095 
1096  shapes_toProcess.append( mapShape );
1097 
1098  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1099 
1100  int nbp;
1101 
1102  if ( !shapes_final.isEmpty() )
1103  {
1104  QLinkedList<LabelPosition*> positions;
1105 
1106  int id = 0; // ids for candidates
1107  double dlx, dly; // delta from label center and bottom-left corner
1108  double alpha = 0.0; // rotation for the label
1109  double px, py;
1110  double dx;
1111  double dy;
1112  int bbid;
1113  double beta;
1114  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1115  double rx, ry;
1116  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1117  j = 0;
1118 
1119  // Compute bounding box foreach finalShape
1120  while ( !shapes_final.isEmpty() )
1121  {
1122  PointSet *shape = shapes_final.takeFirst();
1123  boxes[j] = shape->compute_chull_bbox();
1124 
1125  if ( shape->parent )
1126  delete shape;
1127 
1128  j++;
1129  }
1130 
1131  //dx = dy = min( yrm, xrm ) / 2;
1132  dx = labelWidth / 2.0;
1133  dy = labelHeight / 2.0;
1134 
1135 
1136  int numTry = 0;
1137 
1138  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1139  //then use a smaller limit for number of iterations
1140  int maxTry = mLF->layer()->fitInPolygonOnly() ? 7 : 10;
1141 
1142  do
1143  {
1144  for ( bbid = 0; bbid < j; bbid++ )
1145  {
1146  CHullBox *box = boxes[bbid];
1147 
1148  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1149  {
1150  // Very Large BBOX (should never occur)
1151  continue;
1152  }
1153 
1155  {
1156  //check width/height of bbox is sufficient for label
1157  if ( box->length < labelWidth || box->width < labelHeight )
1158  {
1159  //no way label can fit in this box, skip it
1160  continue;
1161  }
1162  }
1163 
1164  bool enoughPlace = false;
1166  {
1167  enoughPlace = true;
1168  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1169  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1170  int i, j;
1171 
1172  // Virtual label: center on bbox center, label size = 2x original size
1173  // alpha = 0.
1174  // If all corner are in bbox then place candidates horizontaly
1175  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1176  {
1177  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1178  {
1179  if ( !mapShape->containsPoint( rx, ry ) )
1180  {
1181  enoughPlace = false;
1182  break;
1183  }
1184  }
1185  if ( !enoughPlace )
1186  {
1187  break;
1188  }
1189  }
1190 
1191  } // arrangement== FREE ?
1192 
1193  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1194  {
1195  alpha = 0.0; // HORIZ
1196  }
1197  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1198  {
1199  if ( box->alpha <= M_PI / 4 )
1200  {
1201  alpha = box->alpha;
1202  }
1203  else
1204  {
1205  alpha = box->alpha - M_PI / 2;
1206  }
1207  }
1208  else if ( box->length > box->width )
1209  {
1210  alpha = box->alpha - M_PI / 2;
1211  }
1212  else
1213  {
1214  alpha = box->alpha;
1215  }
1216 
1217  beta = atan2( labelHeight, labelWidth ) + alpha;
1218 
1219 
1220  //alpha = box->alpha;
1221 
1222  // delta from label center and down-left corner
1223  dlx = cos( beta ) * diago;
1224  dly = sin( beta ) * diago;
1225 
1226  double px0, py0;
1227 
1228  px0 = box->width / 2.0;
1229  py0 = box->length / 2.0;
1230 
1231  px0 -= ceil( px0 / dx ) * dx;
1232  py0 -= ceil( py0 / dy ) * dy;
1233 
1234  for ( px = px0; px <= box->width; px += dx )
1235  {
1236  for ( py = py0; py <= box->length; py += dy )
1237  {
1238 
1239  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1240  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1241 
1242  rx += box->x[0];
1243  ry += box->y[0];
1244 
1245  bool candidateAcceptable = ( mLF->layer()->fitInPolygonOnly()
1246  ? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1247  : mapShape->containsPoint( rx, ry ) );
1248  if ( candidateAcceptable )
1249  {
1250  // cost is set to minimal value, evaluated later
1251  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1252  }
1253  }
1254  }
1255  } // forall box
1256 
1257  nbp = positions.size();
1258  if ( nbp == 0 )
1259  {
1260  dx /= 2;
1261  dy /= 2;
1262  numTry++;
1263  }
1264  }
1265  while ( nbp == 0 && numTry < maxTry );
1266 
1267  nbp = positions.size();
1268 
1269  for ( i = 0; i < nbp; i++ )
1270  {
1271  lPos << positions.takeFirst();
1272  }
1273 
1274  for ( bbid = 0; bbid < j; bbid++ )
1275  {
1276  delete boxes[bbid];
1277  }
1278 
1279  delete[] boxes;
1280  }
1281  else
1282  {
1283  nbp = 0;
1284  }
1285 
1286  return nbp;
1287 }
1288 
1290  double bboxMin[2], double bboxMax[2],
1291  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1292 {
1293  double bbox[4];
1294 
1295  bbox[0] = bboxMin[0];
1296  bbox[1] = bboxMin[1];
1297  bbox[2] = bboxMax[0];
1298  bbox[3] = bboxMax[1];
1299 
1300  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1301 
1302  if ( mLF->hasFixedPosition() )
1303  {
1304  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1305  }
1306  else
1307  {
1308  switch ( type )
1309  {
1310  case GEOS_POINT:
1312  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1314  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1315  else
1316  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1317  break;
1318  case GEOS_LINESTRING:
1320  createCurvedCandidatesAlongLine( lPos, mapShape );
1321  else
1322  createCandidatesAlongLine( lPos, mapShape );
1323  break;
1324 
1325  case GEOS_POLYGON:
1326  switch ( mLF->layer()->arrangement() )
1327  {
1330  double cx, cy;
1331  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1333  createCandidatesOverPoint( cx, cy, lPos, angle, mapShape );
1334  else
1335  createCandidatesAroundPoint( cx, cy, lPos, angle, mapShape );
1336  break;
1338  createCandidatesAlongLine( lPos, mapShape );
1339  break;
1340  default:
1341  createCandidatesForPolygon( lPos, mapShape );
1342  break;
1343  }
1344  }
1345  }
1346 
1347  // purge candidates that are outside the bbox
1348 
1350  while ( i.hasNext() )
1351  {
1352  LabelPosition* pos = i.next();
1353  bool outside = false;
1354  if ( mLF->layer()->pal->getShowPartial() )
1355  outside = !pos->isIntersect( bbox );
1356  else
1357  outside = !pos->isInside( bbox );
1358  if ( outside )
1359  {
1360  i.remove();
1361  delete pos;
1362  }
1363  else // this one is OK
1364  {
1365  pos->insertIntoIndex( candidates );
1366  }
1367  }
1368 
1369  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1370  return lPos.count();
1371 }
1372 
1373 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1374 {
1375  if ( !mGeos )
1376  createGeosGeom();
1377 
1378  GEOSContextHandle_t ctxt = geosContext();
1379  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1380 
1381  double sizeCost = 0;
1382  if ( geomType == GEOS_LINESTRING )
1383  {
1384  double length;
1385  try
1386  {
1387  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1388  return; // failed to calculate length
1389  }
1390  catch ( GEOSException &e )
1391  {
1392  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1393  return;
1394  }
1395  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1396  if ( length >= bbox_length / 4 )
1397  return; // the line is longer than quarter of height or width - don't penalize it
1398 
1399  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1400  }
1401  else if ( geomType == GEOS_POLYGON )
1402  {
1403  double area;
1404  try
1405  {
1406  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1407  return;
1408  }
1409  catch ( GEOSException &e )
1410  {
1411  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1412  return;
1413  }
1414  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1415  if ( area >= bbox_area / 16 )
1416  return; // covers more than 1/16 of our view - don't penalize it
1417 
1418  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1419  }
1420  else
1421  return; // no size penalty for points
1422 
1423  // apply the penalty
1424  for ( int i = 0; i < nbp; i++ )
1425  {
1426  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1427  }
1428 }
1429 
1431 {
1432  if ( !p2->mGeos )
1433  p2->createGeosGeom();
1434 
1435  try
1436  {
1437  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1438  }
1439  catch ( GEOSException &e )
1440  {
1441  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1442  return false;
1443  }
1444 }
1445 
1447 {
1448  if ( !mGeos )
1449  createGeosGeom();
1450  if ( !other->mGeos )
1451  other->createGeosGeom();
1452 
1453  GEOSContextHandle_t ctxt = geosContext();
1454  try
1455  {
1456  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1457  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1458  GEOSGeometry* geoms[2] = { g1, g2 };
1459  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1460  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1461  GEOSGeom_destroy_r( ctxt, g );
1462 
1463  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1464  {
1465  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1466  GEOSGeom_destroy_r( ctxt, gTmp );
1467  return false;
1468  }
1469  invalidateGeos();
1470 
1471  // set up new geometry
1472  mGeos = gTmp;
1473  mOwnsGeom = true;
1474 
1475  deleteCoords();
1476  qDeleteAll( mHoles );
1477  mHoles.clear();
1478  extractCoords( mGeos );
1479  return true;
1480  }
1481  catch ( GEOSException &e )
1482  {
1483  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1484  return false;
1485  }
1486 }
1487 
1489 {
1490  if ( mLF->alwaysShow() )
1491  {
1492  //if feature is set to always show, bump the priority up by orders of magnitude
1493  //so that other feature's labels are unlikely to be placed over the label for this feature
1494  //(negative numbers due to how pal::extract calculates inactive cost)
1495  return -0.2;
1496  }
1497 
1498  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1499 }
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:234
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:198
Label on top-left of point.
void setCost(double newCost)
Sets the candidate label position&#39;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:587
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
const T & at(int i) const
Candidates are placed in predefined positions around a point.
friend class LabelPosition
Definition: pointset.h:66
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:197
double priority() const
Returns the layer&#39;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:1446
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&#39;s labeling priority.
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1430
void addSizePenalty(int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1373
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:812
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:948
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&#39;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
Label on left of point.
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:854
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1488
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:199
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:941
double ymin
Definition: pointset.h:174
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
double label_height
Definition: feature.h:73
double length() const
Returns length of line geometry.
Definition: pointset.cpp:901
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:59
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
Main class to handle feature.
Definition: feature.h:90
bool containsLabelCandidate(double x, double y, double width, double height, double alpha=0) const
Tests whether a possible label candidate will fit completely within the shape.
Definition: pointset.cpp:290
Offset distance applies from rendered symbol bounds.
bool fitInPolygonOnly() const
Returns whether labels which do not fit completely within a polygon feature are discarded.
Definition: layer.h:226
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:338
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:1083
Pal * pal
Definition: layer.h:260
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:615
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:1289
int createCandidatesOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle, PointSet *mapShape=nullptr)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:230
double x[4]
Definition: pointset.h:50
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
int createCandidatesAroundPoint(double x, double y, QList< LabelPosition * > &lPos, double angle, PointSet *mapShape=nullptr)
Generate candidates for point feature, located around a specified point.
Definition: feature.cpp:433
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:409
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&#39;s name.
Definition: layer.h:86
LineArrangementFlags arrangementFlags() const
Returns the layer&#39;s arrangement flags.
Definition: layer.h:102
QgsLabelFeature * mLF
Definition: feature.h:233
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 xmax
Definition: pointset.h:173
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int orientation, int index, double distance)
Definition: feature.cpp:747
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)
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.