QGIS API Documentation  2.14.5-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  // Shift the character downwards since the draw position is specified at the baseline
883  // and we're calculating the mean line here
884  double dist = 0.9 * li->label_height / 2;
885  if ( orientation < 0 )
886  dist = -dist;
887  start_x += dist * cos( angle + M_PI_2 );
888  start_y -= dist * sin( angle + M_PI_2 );
889 
890  double render_angle = angle;
891 
892  double render_x = start_x;
893  double render_y = start_y;
894 
895  // Center the text on the line
896  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
897  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
898 
899  if ( orientation < 0 )
900  {
901  // rotate in place
902  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
903  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
904  render_angle += M_PI;
905  }
906 
907  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
908  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
909  if ( !slp )
910  slp = tmp;
911  else
912  slp_tmp->setNextPart( tmp );
913  slp_tmp = tmp;
914 
915  //current_placement.add_node(ci.character,render_x, -render_y, render_angle);
916  //current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
917 
918  // Normalise to 0 <= angle < 2PI
919  while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
920  while ( render_angle < 0 ) render_angle += 2 * M_PI;
921 
922  if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
923  upside_down_char_count++;
924  }
925  // END FOR
926 
927  // If we placed too many characters upside down
928  if ( upside_down_char_count >= li->char_num / 2.0 )
929  {
930  // if we auto-detected the orientation then retry with the opposite orientation
931  if ( !orientation_forced )
932  {
933  orientation = -orientation;
934  delete slp;
935  slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
936  }
937  else
938  {
939  // Otherwise we have failed to find a placement
940  delete slp;
941  return nullptr;
942  }
943  }
944 
945  return slp;
946 }
947 
948 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
949 {
950  LabelPosition* newLp = new LabelPosition( *lp );
951  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
952  return newLp;
953 }
954 
956 {
957  LabelInfo* li = mLF->curvedLabelInfo();
958 
959  // label info must be present
960  if ( !li || li->char_num == 0 )
961  return 0;
962 
963  // distance calculation
964  double* path_distances = new double[mapShape->nbPoints];
965  double total_distance = 0;
966  double old_x = -1.0, old_y = -1.0;
967  for ( int i = 0; i < mapShape->nbPoints; i++ )
968  {
969  if ( i == 0 )
970  path_distances[i] = 0;
971  else
972  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
973  old_x = mapShape->x[i];
974  old_y = mapShape->y[i];
975 
976  total_distance += path_distances[i];
977  }
978 
979  if ( qgsDoubleNear( total_distance, 0.0 ) )
980  {
981  delete[] path_distances;
982  return 0;
983  }
984 
985  //calculate overall angle of line
986  double lineAngle;
987  double bx = mapShape->x[0];
988  double by = mapShape->y[0];
989  double ex = mapShape->x[ mapShape->nbPoints - 1 ];
990  double ey = mapShape->y[ mapShape->nbPoints - 1 ];
991  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
992  {
993  lineAngle = 0.0;
994  }
995  else
996  lineAngle = atan2( ey - by, ex - bx );
997 
998  // find out whether the line direction for this candidate is from right to left
999  bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
1000 
1001  QLinkedList<LabelPosition*> positions;
1002  double delta = qMax( li->label_height, total_distance / 10.0 );
1003 
1004  unsigned long flags = mLF->layer()->arrangementFlags();
1005  if ( flags == 0 )
1006  flags = FLAG_ON_LINE; // default flag
1007  // placements may need to be reversed if using line position dependent orientation
1008  // and the line has right-to-left direction
1009  bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1010 
1011  // generate curved labels
1012  for ( int i = 0; i*delta < total_distance; i++ )
1013  {
1014  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, 0, 1, i * delta );
1015 
1016  if ( slp )
1017  {
1018  // evaluate cost
1019  double angle_diff = 0.0, angle_last = 0.0, diff;
1020  LabelPosition* tmp = slp;
1021  double sin_avg = 0, cos_avg = 0;
1022  while ( tmp )
1023  {
1024  if ( tmp != slp ) // not first?
1025  {
1026  diff = fabs( tmp->getAlpha() - angle_last );
1027  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1028  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1029  angle_diff += diff;
1030  }
1031 
1032  sin_avg += sin( tmp->getAlpha() );
1033  cos_avg += cos( tmp->getAlpha() );
1034  angle_last = tmp->getAlpha();
1035  tmp = tmp->getNextPart();
1036  }
1037 
1038  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1039  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1040  if ( cost < 0.0001 ) cost = 0.0001;
1041 
1042  // penalize positions which are further from the line's midpoint
1043  double labelCenter = ( i * delta ) + getLabelWidth() / 2;
1044  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1045  cost += costCenter / 1000; // < 0, 0.0005 >
1046  slp->setCost( cost );
1047 
1048  // average angle is calculated with respect to periodicity of angles
1049  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1050  // displacement
1051  if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
1052  positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 ) );
1053  if ( flags & FLAG_ON_LINE )
1054  positions.append( _createCurvedCandidate( slp, angle_avg, 0 ) );
1055  if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
1056  positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() ) );
1057 
1058  // delete original candidate
1059  delete slp;
1060  }
1061  }
1062 
1063 
1064  int nbp = positions.size();
1065  for ( int i = 0; i < nbp; i++ )
1066  {
1067  lPos << positions.takeFirst();
1068  }
1069 
1070  delete[] path_distances;
1071 
1072  return nbp;
1073 }
1074 
1075 
1076 
1077 
1078 /*
1079  * seg 2
1080  * pt3 ____________pt2
1081  * ¦ ¦
1082  * ¦ ¦
1083  * seg 3 ¦ BBOX ¦ seg 1
1084  * ¦ ¦
1085  * ¦____________¦
1086  * pt0 seg 0 pt1
1087  *
1088  */
1089 
1091 {
1092  int i;
1093  int j;
1094 
1095  double labelWidth = getLabelWidth();
1096  double labelHeight = getLabelHeight();
1097 
1098  QLinkedList<PointSet*> shapes_toProcess;
1099  QLinkedList<PointSet*> shapes_final;
1100 
1101  mapShape->parent = nullptr;
1102 
1103  shapes_toProcess.append( mapShape );
1104 
1105  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1106 
1107  int nbp;
1108 
1109  if ( !shapes_final.isEmpty() )
1110  {
1111  QLinkedList<LabelPosition*> positions;
1112 
1113  int id = 0; // ids for candidates
1114  double dlx, dly; // delta from label center and bottom-left corner
1115  double alpha = 0.0; // rotation for the label
1116  double px, py;
1117  double dx;
1118  double dy;
1119  int bbid;
1120  double beta;
1121  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1122  double rx, ry;
1123  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1124  j = 0;
1125 
1126  // Compute bounding box foreach finalShape
1127  while ( !shapes_final.isEmpty() )
1128  {
1129  PointSet *shape = shapes_final.takeFirst();
1130  boxes[j] = shape->compute_chull_bbox();
1131 
1132  if ( shape->parent )
1133  delete shape;
1134 
1135  j++;
1136  }
1137 
1138  //dx = dy = min( yrm, xrm ) / 2;
1139  dx = labelWidth / 2.0;
1140  dy = labelHeight / 2.0;
1141 
1142 
1143  int numTry = 0;
1144 
1145  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1146  //then use a smaller limit for number of iterations
1147  int maxTry = mLF->layer()->fitInPolygonOnly() ? 7 : 10;
1148 
1149  do
1150  {
1151  for ( bbid = 0; bbid < j; bbid++ )
1152  {
1153  CHullBox *box = boxes[bbid];
1154 
1155  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1156  {
1157  // Very Large BBOX (should never occur)
1158  continue;
1159  }
1160 
1162  {
1163  //check width/height of bbox is sufficient for label
1164  if ( box->length < labelWidth || box->width < labelHeight )
1165  {
1166  //no way label can fit in this box, skip it
1167  continue;
1168  }
1169  }
1170 
1171  bool enoughPlace = false;
1173  {
1174  enoughPlace = true;
1175  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1176  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1177  int i, j;
1178 
1179  // Virtual label: center on bbox center, label size = 2x original size
1180  // alpha = 0.
1181  // If all corner are in bbox then place candidates horizontaly
1182  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1183  {
1184  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1185  {
1186  if ( !mapShape->containsPoint( rx, ry ) )
1187  {
1188  enoughPlace = false;
1189  break;
1190  }
1191  }
1192  if ( !enoughPlace )
1193  {
1194  break;
1195  }
1196  }
1197 
1198  } // arrangement== FREE ?
1199 
1200  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1201  {
1202  alpha = 0.0; // HORIZ
1203  }
1204  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1205  {
1206  if ( box->alpha <= M_PI / 4 )
1207  {
1208  alpha = box->alpha;
1209  }
1210  else
1211  {
1212  alpha = box->alpha - M_PI / 2;
1213  }
1214  }
1215  else if ( box->length > box->width )
1216  {
1217  alpha = box->alpha - M_PI / 2;
1218  }
1219  else
1220  {
1221  alpha = box->alpha;
1222  }
1223 
1224  beta = atan2( labelHeight, labelWidth ) + alpha;
1225 
1226 
1227  //alpha = box->alpha;
1228 
1229  // delta from label center and down-left corner
1230  dlx = cos( beta ) * diago;
1231  dly = sin( beta ) * diago;
1232 
1233  double px0, py0;
1234 
1235  px0 = box->width / 2.0;
1236  py0 = box->length / 2.0;
1237 
1238  px0 -= ceil( px0 / dx ) * dx;
1239  py0 -= ceil( py0 / dy ) * dy;
1240 
1241  for ( px = px0; px <= box->width; px += dx )
1242  {
1243  for ( py = py0; py <= box->length; py += dy )
1244  {
1245 
1246  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1247  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1248 
1249  rx += box->x[0];
1250  ry += box->y[0];
1251 
1252  bool candidateAcceptable = ( mLF->layer()->fitInPolygonOnly()
1253  ? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1254  : mapShape->containsPoint( rx, ry ) );
1255  if ( candidateAcceptable )
1256  {
1257  // cost is set to minimal value, evaluated later
1258  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1259  }
1260  }
1261  }
1262  } // forall box
1263 
1264  nbp = positions.size();
1265  if ( nbp == 0 )
1266  {
1267  dx /= 2;
1268  dy /= 2;
1269  numTry++;
1270  }
1271  }
1272  while ( nbp == 0 && numTry < maxTry );
1273 
1274  nbp = positions.size();
1275 
1276  for ( i = 0; i < nbp; i++ )
1277  {
1278  lPos << positions.takeFirst();
1279  }
1280 
1281  for ( bbid = 0; bbid < j; bbid++ )
1282  {
1283  delete boxes[bbid];
1284  }
1285 
1286  delete[] boxes;
1287  }
1288  else
1289  {
1290  nbp = 0;
1291  }
1292 
1293  return nbp;
1294 }
1295 
1297  double bboxMin[2], double bboxMax[2],
1298  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1299 {
1300  double bbox[4];
1301 
1302  bbox[0] = bboxMin[0];
1303  bbox[1] = bboxMin[1];
1304  bbox[2] = bboxMax[0];
1305  bbox[3] = bboxMax[1];
1306 
1307  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1308 
1309  if ( mLF->hasFixedPosition() )
1310  {
1311  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1312  }
1313  else
1314  {
1315  switch ( type )
1316  {
1317  case GEOS_POINT:
1319  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1321  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1322  else
1323  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1324  break;
1325  case GEOS_LINESTRING:
1327  createCurvedCandidatesAlongLine( lPos, mapShape );
1328  else
1329  createCandidatesAlongLine( lPos, mapShape );
1330  break;
1331 
1332  case GEOS_POLYGON:
1333  switch ( mLF->layer()->arrangement() )
1334  {
1337  double cx, cy;
1338  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1340  createCandidatesOverPoint( cx, cy, lPos, angle, mapShape );
1341  else
1342  createCandidatesAroundPoint( cx, cy, lPos, angle, mapShape );
1343  break;
1345  createCandidatesAlongLine( lPos, mapShape );
1346  break;
1347  default:
1348  createCandidatesForPolygon( lPos, mapShape );
1349  break;
1350  }
1351  }
1352  }
1353 
1354  // purge candidates that are outside the bbox
1355 
1357  while ( i.hasNext() )
1358  {
1359  LabelPosition* pos = i.next();
1360  bool outside = false;
1361  if ( mLF->layer()->pal->getShowPartial() )
1362  outside = !pos->isIntersect( bbox );
1363  else
1364  outside = !pos->isInside( bbox );
1365  if ( outside )
1366  {
1367  i.remove();
1368  delete pos;
1369  }
1370  else // this one is OK
1371  {
1372  pos->insertIntoIndex( candidates );
1373  }
1374  }
1375 
1376  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1377  return lPos.count();
1378 }
1379 
1380 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1381 {
1382  if ( !mGeos )
1383  createGeosGeom();
1384 
1385  GEOSContextHandle_t ctxt = geosContext();
1386  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1387 
1388  double sizeCost = 0;
1389  if ( geomType == GEOS_LINESTRING )
1390  {
1391  double length;
1392  try
1393  {
1394  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1395  return; // failed to calculate length
1396  }
1397  catch ( GEOSException &e )
1398  {
1399  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1400  return;
1401  }
1402  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1403  if ( length >= bbox_length / 4 )
1404  return; // the line is longer than quarter of height or width - don't penalize it
1405 
1406  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1407  }
1408  else if ( geomType == GEOS_POLYGON )
1409  {
1410  double area;
1411  try
1412  {
1413  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1414  return;
1415  }
1416  catch ( GEOSException &e )
1417  {
1418  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1419  return;
1420  }
1421  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1422  if ( area >= bbox_area / 16 )
1423  return; // covers more than 1/16 of our view - don't penalize it
1424 
1425  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1426  }
1427  else
1428  return; // no size penalty for points
1429 
1430  // apply the penalty
1431  for ( int i = 0; i < nbp; i++ )
1432  {
1433  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1434  }
1435 }
1436 
1438 {
1439  if ( !p2->mGeos )
1440  p2->createGeosGeom();
1441 
1442  try
1443  {
1444  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1445  }
1446  catch ( GEOSException &e )
1447  {
1448  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1449  return false;
1450  }
1451 }
1452 
1454 {
1455  if ( !mGeos )
1456  createGeosGeom();
1457  if ( !other->mGeos )
1458  other->createGeosGeom();
1459 
1460  GEOSContextHandle_t ctxt = geosContext();
1461  try
1462  {
1463  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1464  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1465  GEOSGeometry* geoms[2] = { g1, g2 };
1466  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1467  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1468  GEOSGeom_destroy_r( ctxt, g );
1469 
1470  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1471  {
1472  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1473  GEOSGeom_destroy_r( ctxt, gTmp );
1474  return false;
1475  }
1476  invalidateGeos();
1477 
1478  // set up new geometry
1479  mGeos = gTmp;
1480  mOwnsGeom = true;
1481 
1482  deleteCoords();
1483  qDeleteAll( mHoles );
1484  mHoles.clear();
1485  extractCoords( mGeos );
1486  return true;
1487  }
1488  catch ( GEOSException &e )
1489  {
1490  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1491  return false;
1492  }
1493 }
1494 
1496 {
1497  if ( mLF->alwaysShow() )
1498  {
1499  //if feature is set to always show, bump the priority up by orders of magnitude
1500  //so that other feature's labels are unlikely to be placed over the label for this feature
1501  //(negative numbers due to how pal::extract calculates inactive cost)
1502  return -0.2;
1503  }
1504 
1505  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1506 }
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:1453
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:1437
void addSizePenalty(int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1380
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:955
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:1495
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:948
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:1090
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:1296
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:414
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.