QGIS API Documentation  2.99.0-Master (b698612)
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 "qgsgeometryutils.h"
43 #include <QLinkedList>
44 #include <cmath>
45 #include <cfloat>
46 
47 #ifndef M_PI
48 #define M_PI 3.14159265358979323846
49 #endif
50 
51 using namespace pal;
52 
53 FeaturePart::FeaturePart( QgsLabelFeature *feat, const GEOSGeometry *geom )
54  : mLF( feat )
55 {
56  // we'll remove const, but we won't modify that geometry
57  mGeos = const_cast<GEOSGeometry *>( geom );
58  mOwnsGeom = false; // geometry is owned by Feature class
59 
60  extractCoords( geom );
61 
62  holeOf = nullptr;
63  for ( int i = 0; i < mHoles.count(); i++ )
64  {
65  mHoles.at( i )->holeOf = this;
66  }
67 
68 }
69 
71  : PointSet( other )
72  , mLF( other.mLF )
73 {
74  Q_FOREACH ( const FeaturePart *hole, other.mHoles )
75  {
76  mHoles << new FeaturePart( *hole );
77  mHoles.last()->holeOf = this;
78  }
79 }
80 
82 {
83  // X and Y are deleted in PointSet
84 
85  qDeleteAll( mHoles );
86  mHoles.clear();
87 }
88 
89 void FeaturePart::extractCoords( const GEOSGeometry *geom )
90 {
91  const GEOSCoordSequence *coordSeq = nullptr;
92  GEOSContextHandle_t geosctxt = geosContext();
93 
94  type = GEOSGeomTypeId_r( geosctxt, geom );
95 
96  if ( type == GEOS_POLYGON )
97  {
98  if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
99  {
100  int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
101 
102  for ( int i = 0; i < numHoles; ++i )
103  {
104  const GEOSGeometry *interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
105  FeaturePart *hole = new FeaturePart( mLF, interior );
106  hole->holeOf = nullptr;
107  // possibly not needed. it's not done for the exterior ring, so I'm not sure
108  // why it's just done here...
109  GeomFunction::reorderPolygon( hole->nbPoints, hole->x, hole->y );
110 
111  mHoles << hole;
112  }
113  }
114 
115  // use exterior ring for the extraction of coordinates that follows
116  geom = GEOSGetExteriorRing_r( geosctxt, geom );
117  }
118  else
119  {
120  qDeleteAll( mHoles );
121  mHoles.clear();
122  }
123 
124  // find out number of points
125  nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
126  coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
127 
128  // initialize bounding box
129  xmin = ymin = DBL_MAX;
130  xmax = ymax = -DBL_MAX;
131 
132  // initialize coordinate arrays
133  deleteCoords();
134  x = new double[nbPoints];
135  y = new double[nbPoints];
136 
137  for ( int i = 0; i < nbPoints; ++i )
138  {
139  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] );
140  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] );
141 
142  xmax = x[i] > xmax ? x[i] : xmax;
143  xmin = x[i] < xmin ? x[i] : xmin;
144 
145  ymax = y[i] > ymax ? y[i] : ymax;
146  ymin = y[i] < ymin ? y[i] : ymin;
147  }
148 }
149 
151 {
152  return mLF->layer();
153 }
154 
156 {
157  return mLF->id();
158 }
159 
161 {
162  if ( !part )
163  return false;
164 
165  if ( mLF->layer()->name() != part->layer()->name() )
166  return false;
167 
168  if ( mLF->id() == part->featureId() )
169  return true;
170 
171  // any part of joined features are also treated as having the same label feature
172  int connectedFeatureId = mLF->layer()->connectedFeatureId( mLF->id() );
173  if ( connectedFeatureId >= 0 && connectedFeatureId == mLF->layer()->connectedFeatureId( part->featureId() ) )
174  return true;
175 
176  return false;
177 }
178 
179 LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
180 {
181  QPointF quadOffset = mLF->quadOffset();
182  qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
183 
184  if ( quadOffsetX < 0 )
185  {
186  if ( quadOffsetY < 0 )
187  {
189  }
190  else if ( quadOffsetY > 0 )
191  {
193  }
194  else
195  {
197  }
198  }
199  else if ( quadOffsetX > 0 )
200  {
201  if ( quadOffsetY < 0 )
202  {
204  }
205  else if ( quadOffsetY > 0 )
206  {
208  }
209  else
210  {
212  }
213  }
214  else
215  {
216  if ( quadOffsetY < 0 )
217  {
219  }
220  else if ( quadOffsetY > 0 )
221  {
223  }
224  else
225  {
227  }
228  }
229 }
230 
231 int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosition *> &lPos, double angle )
232 {
233  int nbp = 1;
234 
235  // get from feature
236  double labelW = getLabelWidth();
237  double labelH = getLabelHeight();
238 
239  double cost = 0.0001;
240  int id = 0;
241 
242  double xdiff = -labelW / 2.0;
243  double ydiff = -labelH / 2.0;
244 
245  if ( !qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
246  {
247  xdiff += labelW / 2.0 * mLF->quadOffset().x();
248  }
249  if ( !qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
250  {
251  ydiff += labelH / 2.0 * mLF->quadOffset().y();
252  }
253 
254  if ( ! mLF->hasFixedPosition() )
255  {
256  if ( !qgsDoubleNear( angle, 0.0 ) )
257  {
258  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
259  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
260  xdiff = xd;
261  ydiff = yd;
262  }
263  }
264 
266  {
267  //if in "around point" placement mode, then we use the label distance to determine
268  //the label's offset
269  if ( qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
270  {
271  ydiff += mLF->quadOffset().y() * mLF->distLabel();
272  }
273  else if ( qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
274  {
275  xdiff += mLF->quadOffset().x() * mLF->distLabel();
276  }
277  else
278  {
279  xdiff += mLF->quadOffset().x() * M_SQRT1_2 * mLF->distLabel();
280  ydiff += mLF->quadOffset().y() * M_SQRT1_2 * mLF->distLabel();
281  }
282  }
283  else
284  {
285  if ( !qgsDoubleNear( mLF->positionOffset().x(), 0.0 ) )
286  {
287  xdiff += mLF->positionOffset().x();
288  }
289  if ( !qgsDoubleNear( mLF->positionOffset().y(), 0.0 ) )
290  {
291  ydiff += mLF->positionOffset().y();
292  }
293  }
294 
295  double lx = x + xdiff;
296  double ly = y + ydiff;
297 
298  if ( mLF->permissibleZonePrepared() )
299  {
300  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
301  {
302  return 0;
303  }
304  }
305 
306  lPos << new LabelPosition( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() );
307  return nbp;
308 }
309 
310 int FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y, QList<LabelPosition *> &lPos, double angle )
311 {
312  QVector< QgsPalLayerSettings::PredefinedPointPosition > positions = mLF->predefinedPositionOrder();
313  double labelWidth = getLabelWidth();
314  double labelHeight = getLabelHeight();
315  double distanceToLabel = getLabelDistance();
316  const QgsMargins &visualMargin = mLF->visualMargin();
317 
318  double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
319  double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );
320 
321  double cost = 0.0001;
322  int i = 0;
323  Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
324  {
325  double alpha = 0.0;
326  double deltaX = 0;
327  double deltaY = 0;
329  switch ( position )
330  {
333  alpha = 3 * M_PI_4;
334  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
335  deltaY = -visualMargin.bottom() + symbolHeightOffset;
336  break;
337 
339  quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
340  alpha = M_PI_2;
341  deltaX = -labelWidth / 4.0 - visualMargin.left();
342  deltaY = -visualMargin.bottom() + symbolHeightOffset;
343  break;
344 
346  quadrant = LabelPosition::QuadrantAbove;
347  alpha = M_PI_2;
348  deltaX = -labelWidth / 2.0;
349  deltaY = -visualMargin.bottom() + symbolHeightOffset;
350  break;
351 
353  quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
354  alpha = M_PI_2;
355  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right();
356  deltaY = -visualMargin.bottom() + symbolHeightOffset;
357  break;
358 
361  alpha = M_PI_4;
362  deltaX = - visualMargin.left() + symbolWidthOffset;
363  deltaY = -visualMargin.bottom() + symbolHeightOffset;
364  break;
365 
367  quadrant = LabelPosition::QuadrantLeft;
368  alpha = M_PI;
369  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
370  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
371  break;
372 
374  quadrant = LabelPosition::QuadrantRight;
375  alpha = 0.0;
376  deltaX = -visualMargin.left() + symbolWidthOffset;
377  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
378  break;
379 
382  alpha = 5 * M_PI_4;
383  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
384  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
385  break;
386 
388  quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
389  alpha = 3 * M_PI_2;
390  deltaX = -labelWidth / 4.0 - visualMargin.left();
391  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
392  break;
393 
395  quadrant = LabelPosition::QuadrantBelow;
396  alpha = 3 * M_PI_2;
397  deltaX = -labelWidth / 2.0;
398  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
399  break;
400 
402  quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
403  alpha = 3 * M_PI_2;
404  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right();
405  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
406  break;
407 
410  alpha = 7 * M_PI_4;
411  deltaX = -visualMargin.left() + symbolWidthOffset;
412  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
413  break;
414  }
415 
416  //have bearing, distance - calculate reference point
417  double referenceX = cos( alpha ) * distanceToLabel + x;
418  double referenceY = sin( alpha ) * distanceToLabel + y;
419 
420  double labelX = referenceX + deltaX;
421  double labelY = referenceY + deltaY;
422 
423  if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
424  {
425  lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
426  //TODO - tweak
427  cost += 0.001;
428  }
429 
430  ++i;
431  }
432 
433  return lPos.count();
434 }
435 
436 int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPosition * > &lPos, double angle )
437 {
438  double labelWidth = getLabelWidth();
439  double labelHeight = getLabelHeight();
440  double distanceToLabel = getLabelDistance();
441 
442  int numberCandidates = mLF->layer()->pal->point_p;
443 
444  int icost = 0;
445  int inc = 2;
446 
447  double candidateAngleIncrement = 2 * M_PI / numberCandidates; /* angle bw 2 pos */
448 
449  /* various angles */
450  double a90 = M_PI / 2;
451  double a180 = M_PI;
452  double a270 = a180 + a90;
453  double a360 = 2 * M_PI;
454 
455  double gamma1, gamma2;
456 
457  if ( distanceToLabel > 0 )
458  {
459  gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
460  gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
461  }
462  else
463  {
464  gamma1 = gamma2 = a90 / 3.0;
465  }
466 
467  if ( gamma1 > a90 / 3.0 )
468  gamma1 = a90 / 3.0;
469 
470  if ( gamma2 > a90 / 3.0 )
471  gamma2 = a90 / 3.0;
472 
473  QList< LabelPosition * > candidates;
474 
475  int i;
476  double angleToCandidate;
477  for ( i = 0, angleToCandidate = M_PI / 4; i < numberCandidates; i++, angleToCandidate += candidateAngleIncrement )
478  {
479  double labelX = x;
480  double labelY = y;
481 
482  if ( angleToCandidate > a360 )
483  angleToCandidate -= a360;
484 
486 
487  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
488  {
489  labelX += distanceToLabel;
490  double iota = ( angleToCandidate + gamma1 );
491  if ( iota > a360 - gamma1 )
492  iota -= a360;
493 
494  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
495  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
496 
497  quadrant = LabelPosition::QuadrantRight;
498  }
499  else if ( angleToCandidate < a90 - gamma2 ) // top-right
500  {
501  labelX += distanceToLabel * cos( angleToCandidate );
502  labelY += distanceToLabel * sin( angleToCandidate );
504  }
505  else if ( angleToCandidate < a90 + gamma2 ) // top
506  {
507  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
508  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
509  labelY += distanceToLabel;
510  quadrant = LabelPosition::QuadrantAbove;
511  }
512  else if ( angleToCandidate < a180 - gamma1 ) // top left
513  {
514  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
515  labelY += distanceToLabel * sin( angleToCandidate );
517  }
518  else if ( angleToCandidate < a180 + gamma1 ) // left
519  {
520  labelX += -distanceToLabel - labelWidth;
521  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
522  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
523  quadrant = LabelPosition::QuadrantLeft;
524  }
525  else if ( angleToCandidate < a270 - gamma2 ) // down - left
526  {
527  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
528  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
530  }
531  else if ( angleToCandidate < a270 + gamma2 ) // down
532  {
533  labelY += -distanceToLabel - labelHeight;
534  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
535  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
536  quadrant = LabelPosition::QuadrantBelow;
537  }
538  else if ( angleToCandidate < a360 ) // down - right
539  {
540  labelX += distanceToLabel * cos( angleToCandidate );
541  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
543  }
544 
545  double cost;
546 
547  if ( numberCandidates == 1 )
548  cost = 0.0001;
549  else
550  cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
551 
552 
553  if ( mLF->permissibleZonePrepared() )
554  {
555  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
556  {
557  continue;
558  }
559  }
560 
561  candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
562 
563  icost += inc;
564 
565  if ( icost == numberCandidates )
566  {
567  icost = numberCandidates - 1;
568  inc = -2;
569  }
570  else if ( icost > numberCandidates )
571  {
572  icost = numberCandidates - 2;
573  inc = -2;
574  }
575 
576  }
577 
578  if ( !candidates.isEmpty() )
579  {
580  for ( int i = 0; i < candidates.count(); ++i )
581  {
582  lPos << candidates.at( i );
583  }
584  }
585 
586  return candidates.count();
587 }
588 
589 int FeaturePart::createCandidatesAlongLine( QList< LabelPosition * > &lPos, PointSet *mapShape )
590 {
591  //prefer to label along straightish segments:
592  int candidates = createCandidatesAlongLineNearStraightSegments( lPos, mapShape );
593 
594  if ( candidates < mLF->layer()->pal->line_p )
595  {
596  // but not enough candidates yet, so fallback to labeling near whole line's midpoint
597  candidates = createCandidatesAlongLineNearMidpoint( lPos, mapShape, candidates > 0 ? 0.01 : 0.0 );
598  }
599  return candidates;
600 }
601 
602 int FeaturePart::createCandidatesAlongLineNearStraightSegments( QList<LabelPosition *> &lPos, PointSet *mapShape )
603 {
604  double labelWidth = getLabelWidth();
605  double labelHeight = getLabelHeight();
606  double distanceLineToLabel = getLabelDistance();
607  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
608  if ( flags == 0 )
609  flags = FLAG_ON_LINE; // default flag
610 
611  // first scan through the whole line and look for segments where the angle at a node is greater than 45 degrees - these form a "hard break" which labels shouldn't cross over
612  QVector< int > extremeAngleNodes;
613  PointSet *line = mapShape;
614  int numberNodes = line->nbPoints;
615  double *x = line->x;
616  double *y = line->y;
617 
618  // closed line? if so, we need to handle the final node angle
619  bool closedLine = qgsDoubleNear( x[0], x[ numberNodes - 1] ) && qgsDoubleNear( y[0], y[numberNodes - 1 ] );
620  for ( int i = 1; i <= numberNodes - ( closedLine ? 1 : 2 ); ++i )
621  {
622  double x1 = x[i - 1];
623  double x2 = x[i];
624  double x3 = x[ i == numberNodes - 1 ? 1 : i + 1]; // wraparound for closed linestrings
625  double y1 = y[i - 1];
626  double y2 = y[i];
627  double y3 = y[ i == numberNodes - 1 ? 1 : i + 1]; // wraparound for closed linestrings
628  if ( qgsDoubleNear( y2, y3 ) && qgsDoubleNear( x2, x3 ) )
629  continue;
630  if ( qgsDoubleNear( y1, y2 ) && qgsDoubleNear( x1, x2 ) )
631  continue;
632  double vertexAngle = M_PI - ( atan2( y3 - y2, x3 - x2 ) - atan2( y2 - y1, x2 - x1 ) );
633  vertexAngle = QgsGeometryUtils::normalizedAngle( vertexAngle );
634 
635  // extreme angles form more than 45 degree angle at a node - these are the ones we don't want labels to cross
636  if ( vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0 )
637  extremeAngleNodes << i;
638  }
639  extremeAngleNodes << numberNodes - 1;
640 
641  if ( extremeAngleNodes.isEmpty() )
642  {
643  // no extreme angles - createCandidatesAlongLineNearMidpoint will be more appropriate
644  return 0;
645  }
646 
647  // calculate lengths of segments, and work out longest straight-ish segment
648  double *segmentLengths = new double[ numberNodes - 1 ]; // segments lengths distance bw pt[i] && pt[i+1]
649  double *distanceToSegment = new double[ numberNodes ]; // absolute distance bw pt[0] and pt[i] along the line
650  double totalLineLength = 0.0;
651  QVector< double > straightSegmentLengths;
652  QVector< double > straightSegmentAngles;
653  straightSegmentLengths.reserve( extremeAngleNodes.size() + 1 );
654  straightSegmentAngles.reserve( extremeAngleNodes.size() + 1 );
655  double currentStraightSegmentLength = 0;
656  double longestSegmentLength = 0;
657  int segmentIndex = 0;
658  double segmentStartX = x[0];
659  double segmentStartY = y[0];
660  for ( int i = 0; i < numberNodes - 1; i++ )
661  {
662  if ( i == 0 )
663  distanceToSegment[i] = 0;
664  else
665  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
666 
667  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
668  totalLineLength += segmentLengths[i];
669  if ( extremeAngleNodes.contains( i ) )
670  {
671  // at an extreme angle node, so reset counters
672  straightSegmentLengths << currentStraightSegmentLength;
673  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( atan2( y[i] - segmentStartY, x[i] - segmentStartX ) );
674  longestSegmentLength = qMax( longestSegmentLength, currentStraightSegmentLength );
675  segmentIndex++;
676  currentStraightSegmentLength = 0;
677  segmentStartX = x[i];
678  segmentStartY = y[i];
679  }
680  currentStraightSegmentLength += segmentLengths[i];
681  }
682  distanceToSegment[line->nbPoints - 1] = totalLineLength;
683  straightSegmentLengths << currentStraightSegmentLength;
684  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( atan2( y[numberNodes - 1] - segmentStartY, x[numberNodes - 1] - segmentStartX ) );
685  longestSegmentLength = qMax( longestSegmentLength, currentStraightSegmentLength );
686  double middleOfLine = totalLineLength / 2.0;
687 
688  if ( totalLineLength < labelWidth )
689  {
690  delete[] segmentLengths;
691  delete[] distanceToSegment;
692  return 0; //createCandidatesAlongLineNearMidpoint will be more appropriate
693  }
694 
695  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
696  lineStepDistance = qMin( qMin( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
697 
698  double distanceToEndOfSegment = 0.0;
699  int lastNodeInSegment = 0;
700  // finally, loop through all these straight segments. For each we create candidates along the straight segment.
701  for ( int i = 0; i < straightSegmentLengths.count(); ++i )
702  {
703  currentStraightSegmentLength = straightSegmentLengths.at( i );
704  double currentSegmentAngle = straightSegmentAngles.at( i );
705  lastNodeInSegment = extremeAngleNodes.at( i );
706  double distanceToStartOfSegment = distanceToEndOfSegment;
707  distanceToEndOfSegment = distanceToSegment[ lastNodeInSegment ];
708  double distanceToCenterOfSegment = 0.5 * ( distanceToEndOfSegment + distanceToStartOfSegment );
709 
710  if ( currentStraightSegmentLength < labelWidth )
711  // can't fit a label on here
712  continue;
713 
714  double currentDistanceAlongLine = distanceToStartOfSegment;
715  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
716  double candidateLength = 0.0;
717  double cost = 0.0;
718  double angle = 0.0;
719  double beta = 0.0;
720 
721  //calculate some cost penalties
722  double segmentCost = 1.0 - ( distanceToEndOfSegment - distanceToStartOfSegment ) / longestSegmentLength; // 0 -> 1 (lower for longer segments)
723  double segmentAngleCost = 1 - qAbs( fmod( currentSegmentAngle, M_PI ) - M_PI_2 ) / M_PI_2; // 0 -> 1, lower for more horizontal segments
724 
725  while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
726  {
727  // calculate positions along linestring corresponding to start and end of current label candidate
728  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
729  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
730 
731  candidateLength = sqrt( ( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
732 
733 
734  // LOTS OF DIFFERENT COSTS TO BALANCE HERE - feel free to tweak these, but please add a unit test
735  // which covers the situation you are adjusting for (e.g., "given equal length lines, choose the more horizontal line")
736 
737  cost = candidateLength / labelWidth;
738  if ( cost > 0.98 )
739  cost = 0.0001;
740  else
741  {
742  // jaggy line has a greater cost
743  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
744  }
745 
746  // penalize positions which are further from the straight segments's midpoint
747  double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
748  double costCenter = 2 * qAbs( labelCenter - distanceToCenterOfSegment ) / ( distanceToEndOfSegment - distanceToStartOfSegment ); // 0 -> 1
749  cost += costCenter * 0.0005; // < 0, 0.0005 >
750 
751  if ( !closedLine )
752  {
753  // penalize positions which are further from absolute center of whole linestring
754  // this only applies to non closed linestrings, since the middle of a closed linestring is effectively arbitrary
755  // and irrelevant to labeling
756  double costLineCenter = 2 * qAbs( labelCenter - middleOfLine ) / totalLineLength; // 0 -> 1
757  cost += costLineCenter * 0.0005; // < 0, 0.0005 >
758  }
759 
760  cost += segmentCost * 0.0005; // prefer labels on longer straight segments
761  cost += segmentAngleCost * 0.0001; // prefer more horizontal segments, but this is less important than length considerations
762 
763  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
764  {
765  angle = 0.0;
766  }
767  else
768  angle = atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
769 
770  beta = angle + M_PI / 2;
771 
773  {
774  // find out whether the line direction for this candidate is from right to left
775  bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
776  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
777  bool reversed = ( ( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
778  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
779  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
780 
781  double placementCost = 0.0;
782  if ( belowLine )
783  {
784  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
785  {
786  lPos.append( new LabelPosition( i, candidateStartX - cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
787  placementCost += 0.001;
788  }
789  }
790  if ( aboveLine )
791  {
792  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
793  {
794  lPos.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
795  placementCost += 0.001;
796  }
797  }
798  if ( flags & FLAG_ON_LINE )
799  {
800  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * cos( beta ) / 2, candidateStartY - labelHeight * sin( beta ) / 2, labelWidth, labelHeight, angle ) )
801  lPos.append( new LabelPosition( i, candidateStartX - labelHeight * cos( beta ) / 2, candidateStartY - labelHeight * sin( beta ) / 2, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
802  }
803  }
805  {
806  lPos.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
807  }
808  else
809  {
810  // an invalid arrangement?
811  }
812 
813  currentDistanceAlongLine += lineStepDistance;
814  }
815  }
816 
817  delete[] segmentLengths;
818  delete[] distanceToSegment;
819  return lPos.size();
820 }
821 
822 int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition *> &lPos, PointSet *mapShape, double initialCost )
823 {
824  double distanceLineToLabel = getLabelDistance();
825 
826  double labelWidth = getLabelWidth();
827  double labelHeight = getLabelHeight();
828 
829  double angle;
830  double cost;
831 
832  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
833  if ( flags == 0 )
834  flags = FLAG_ON_LINE; // default flag
835 
836  QList<LabelPosition *> positions;
837 
838  PointSet *line = mapShape;
839  int nbPoints = line->nbPoints;
840  double *x = line->x;
841  double *y = line->y;
842 
843  double *segmentLengths = new double[nbPoints - 1]; // segments lengths distance bw pt[i] && pt[i+1]
844  double *distanceToSegment = new double[nbPoints]; // absolute distance bw pt[0] and pt[i] along the line
845 
846  double totalLineLength = 0.0; // line length
847  for ( int i = 0; i < line->nbPoints - 1; i++ )
848  {
849  if ( i == 0 )
850  distanceToSegment[i] = 0;
851  else
852  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
853 
854  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
855  totalLineLength += segmentLengths[i];
856  }
857  distanceToSegment[line->nbPoints - 1] = totalLineLength;
858 
859  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
860  double currentDistanceAlongLine = 0;
861 
862  if ( totalLineLength > labelWidth )
863  {
864  lineStepDistance = qMin( qMin( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
865  }
866  else // line length < label width => centering label position
867  {
868  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
869  lineStepDistance = -1;
870  totalLineLength = labelWidth;
871  }
872 
873  double candidateLength;
874  double beta;
875  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
876  int i = 0;
877  while ( currentDistanceAlongLine < totalLineLength - labelWidth )
878  {
879  // calculate positions along linestring corresponding to start and end of current label candidate
880  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
881  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
882 
883  if ( currentDistanceAlongLine < 0 )
884  {
885  // label is bigger than line, use whole available line
886  candidateLength = sqrt( ( x[nbPoints - 1] - x[0] ) * ( x[nbPoints - 1] - x[0] )
887  + ( y[nbPoints - 1] - y[0] ) * ( y[nbPoints - 1] - y[0] ) );
888  }
889  else
890  {
891  candidateLength = sqrt( ( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
892  }
893 
894  cost = candidateLength / labelWidth;
895  if ( cost > 0.98 )
896  cost = 0.0001;
897  else
898  {
899  // jaggy line has a greater cost
900  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
901  }
902 
903  // penalize positions which are further from the line's midpoint
904  double costCenter = qAbs( totalLineLength / 2 - ( currentDistanceAlongLine + labelWidth / 2 ) ) / totalLineLength; // <0, 0.5>
905  cost += costCenter / 1000; // < 0, 0.0005 >
906  cost += initialCost;
907 
908  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
909  {
910  angle = 0.0;
911  }
912  else
913  angle = atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
914 
915  beta = angle + M_PI / 2;
916 
918  {
919  // find out whether the line direction for this candidate is from right to left
920  bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
921  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
922  bool reversed = ( ( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
923  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
924  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
925 
926  if ( aboveLine )
927  {
928  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
929  positions.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
930  }
931  if ( belowLine )
932  {
933  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
934  positions.append( new LabelPosition( i, candidateStartX - cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
935  }
936  if ( flags & FLAG_ON_LINE )
937  {
938  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * cos( beta ) / 2, candidateStartY - labelHeight * sin( beta ) / 2, labelWidth, labelHeight, angle ) )
939  positions.append( new LabelPosition( i, candidateStartX - labelHeight * cos( beta ) / 2, candidateStartY - labelHeight * sin( beta ) / 2, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
940  }
941  }
943  {
944  positions.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
945  }
946  else
947  {
948  // an invalid arrangement?
949  }
950 
951  currentDistanceAlongLine += lineStepDistance;
952 
953  i++;
954 
955  if ( lineStepDistance < 0 )
956  break;
957  }
958 
959  //delete line;
960 
961  delete[] segmentLengths;
962  delete[] distanceToSegment;
963 
964  lPos.append( positions );
965  return lPos.size();
966 }
967 
968 
969 LabelPosition *FeaturePart::curvedPlacementAtOffset( PointSet *path_positions, double *path_distances, int &orientation, int index, double distance, bool &reversed, bool &flip )
970 {
971  // Check that the given distance is on the given index and find the correct index and distance if not
972  while ( distance < 0 && index > 1 )
973  {
974  index--;
975  distance += path_distances[index];
976  }
977 
978  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
979  {
980  return nullptr;
981  }
982 
983  // Same thing, checking if we go off the end
984  while ( index < path_positions->nbPoints && distance > path_distances[index] )
985  {
986  distance -= path_distances[index];
987  index += 1;
988  }
989  if ( index >= path_positions->nbPoints )
990  {
991  return nullptr;
992  }
993 
994  LabelInfo *li = mLF->curvedLabelInfo();
995 
996  double string_height = li->label_height;
997 
998  double segment_length = path_distances[index];
999  if ( qgsDoubleNear( segment_length, 0.0 ) )
1000  {
1001  // Not allowed to place across on 0 length segments or discontinuities
1002  return nullptr;
1003  }
1004 
1005  if ( orientation == 0 ) // Must be map orientation
1006  {
1007  // Calculate the orientation based on the angle of the path segment under consideration
1008 
1009  double _distance = distance;
1010  int endindex = index;
1011 
1012  for ( int i = 0; i < li->char_num; i++ )
1013  {
1014  LabelInfo::CharacterInfo &ci = li->char_info[i];
1015  double start_x, start_y, end_x, end_y;
1016  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, endindex, _distance, start_x, start_y, end_x, end_y ) )
1017  {
1018  return nullptr;
1019  }
1020  }
1021 
1022  // Determine the angle of the path segment under consideration
1023  double dx = path_positions->x[endindex] - path_positions->x[index];
1024  double dy = path_positions->y[endindex] - path_positions->y[index];
1025  double line_angle = atan2( -dy, dx );
1026 
1027  bool isRightToLeft = ( line_angle > 0.55 * M_PI || line_angle < -0.45 * M_PI );
1028  reversed = isRightToLeft;
1029  orientation = isRightToLeft ? -1 : 1;
1030  }
1031 
1032  if ( !showUprightLabels() )
1033  {
1034  if ( orientation < 0 )
1035  {
1036  flip = true; // Report to the caller, that the orientation is flipped
1037  reversed = !reversed;
1038  orientation = 1;
1039  }
1040  }
1041 
1042  LabelPosition *slp = nullptr;
1043  LabelPosition *slp_tmp = nullptr;
1044 
1045  double old_x = path_positions->x[index - 1];
1046  double old_y = path_positions->y[index - 1];
1047 
1048  double new_x = path_positions->x[index];
1049  double new_y = path_positions->y[index];
1050 
1051  double dx = new_x - old_x;
1052  double dy = new_y - old_y;
1053 
1054  double angle = atan2( -dy, dx );
1055 
1056  for ( int i = 0; i < li->char_num; i++ )
1057  {
1058  double last_character_angle = angle;
1059 
1060  // grab the next character according to the orientation
1061  LabelInfo::CharacterInfo &ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num - i - 1] );
1062  if ( qgsDoubleNear( ci.width, 0.0 ) )
1063  // Certain scripts rely on zero-width character, skip those to prevent failure (see #15801)
1064  continue;
1065 
1066  double start_x, start_y, end_x, end_y;
1067  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) )
1068  {
1069  delete slp;
1070  return nullptr;
1071  }
1072 
1073  // Calculate angle from the start of the character to the end based on start_/end_ position
1074  angle = atan2( start_y - end_y, end_x - start_x );
1075 
1076  // Test last_character_angle vs angle
1077  // since our rendering angle has changed then check against our
1078  // max allowable angle change.
1079  double angle_delta = last_character_angle - angle;
1080  // normalise between -180 and 180
1081  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1082  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1083  if ( ( li->max_char_angle_inside > 0 && angle_delta > 0
1084  && angle_delta > li->max_char_angle_inside * ( M_PI / 180 ) )
1085  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1086  && angle_delta < li->max_char_angle_outside * ( M_PI / 180 ) ) )
1087  {
1088  delete slp;
1089  return nullptr;
1090  }
1091 
1092  // Shift the character downwards since the draw position is specified at the baseline
1093  // and we're calculating the mean line here
1094  double dist = 0.9 * li->label_height / 2;
1095  if ( orientation < 0 )
1096  {
1097  dist = -dist;
1098  flip = true;
1099  }
1100  start_x += dist * cos( angle + M_PI_2 );
1101  start_y -= dist * sin( angle + M_PI_2 );
1102 
1103  double render_angle = angle;
1104 
1105  double render_x = start_x;
1106  double render_y = start_y;
1107 
1108  // Center the text on the line
1109  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1110  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1111 
1112  if ( orientation < 0 )
1113  {
1114  // rotate in place
1115  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
1116  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1117  render_angle += M_PI;
1118  }
1119 
1120  LabelPosition *tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1121  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1122  if ( !slp )
1123  slp = tmp;
1124  else
1125  slp_tmp->setNextPart( tmp );
1126  slp_tmp = tmp;
1127 
1128  // Normalise to 0 <= angle < 2PI
1129  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1130  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1131 
1132  if ( render_angle > M_PI / 2 && render_angle < 1.5 * M_PI )
1134  }
1135  // END FOR
1136 
1137  return slp;
1138 }
1139 
1140 static LabelPosition *_createCurvedCandidate( LabelPosition *lp, double angle, double dist )
1141 {
1142  LabelPosition *newLp = new LabelPosition( *lp );
1143  newLp->offsetPosition( dist * cos( angle + M_PI / 2 ), dist * sin( angle + M_PI / 2 ) );
1144  return newLp;
1145 }
1146 
1147 int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition * > &lPos, PointSet *mapShape )
1148 {
1149  LabelInfo *li = mLF->curvedLabelInfo();
1150 
1151  // label info must be present
1152  if ( !li || li->char_num == 0 )
1153  return 0;
1154 
1155  // distance calculation
1156  double *path_distances = new double[mapShape->nbPoints];
1157  double total_distance = 0;
1158  double old_x = -1.0, old_y = -1.0;
1159  for ( int i = 0; i < mapShape->nbPoints; i++ )
1160  {
1161  if ( i == 0 )
1162  path_distances[i] = 0;
1163  else
1164  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
1165  old_x = mapShape->x[i];
1166  old_y = mapShape->y[i];
1167 
1168  total_distance += path_distances[i];
1169  }
1170 
1171  if ( qgsDoubleNear( total_distance, 0.0 ) )
1172  {
1173  delete[] path_distances;
1174  return 0;
1175  }
1176 
1177  QLinkedList<LabelPosition *> positions;
1178  double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );
1179 
1180  unsigned long flags = mLF->layer()->arrangementFlags();
1181  if ( flags == 0 )
1182  flags = FLAG_ON_LINE; // default flag
1183 
1184  // generate curved labels
1185  for ( double i = 0; i < total_distance; i += delta )
1186  {
1187  bool flip = false;
1188  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1189  bool reversed = false;
1190 
1191  // an orientation of 0 means try both orientations and choose the best
1192  int orientation = 0;
1193  if ( !( flags & FLAG_MAP_ORIENTATION ) )
1194  {
1195  //... but if we are using line orientation flags, then we can only accept a single orientation,
1196  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1197  orientation = 1;
1198  }
1199 
1200  LabelPosition *slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1201  if ( slp == nullptr )
1202  continue;
1203 
1204  // If we placed too many characters upside down
1205  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1206  {
1207  // if labels should be shown upright then retry with the opposite orientation
1208  if ( ( showUprightLabels() && !flip ) )
1209  {
1210  delete slp;
1211  orientation = -orientation;
1212  slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1213  }
1214  }
1215  if ( slp == nullptr )
1216  continue;
1217 
1218  // evaluate cost
1219  double angle_diff = 0.0, angle_last = 0.0, diff;
1220  LabelPosition *tmp = slp;
1221  double sin_avg = 0, cos_avg = 0;
1222  while ( tmp )
1223  {
1224  if ( tmp != slp ) // not first?
1225  {
1226  diff = fabs( tmp->getAlpha() - angle_last );
1227  if ( diff > 2 * M_PI ) diff -= 2 * M_PI;
1228  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1229  angle_diff += diff;
1230  }
1231 
1232  sin_avg += sin( tmp->getAlpha() );
1233  cos_avg += cos( tmp->getAlpha() );
1234  angle_last = tmp->getAlpha();
1235  tmp = tmp->getNextPart();
1236  }
1237 
1238  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1239  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1240  if ( cost < 0.0001 ) cost = 0.0001;
1241 
1242  // penalize positions which are further from the line's midpoint
1243  double labelCenter = i + getLabelWidth() / 2;
1244  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1245  cost += costCenter / 1000; // < 0, 0.0005 >
1246  slp->setCost( cost );
1247 
1248  // average angle is calculated with respect to periodicity of angles
1249  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1250  bool localreversed = flip ? !reversed : reversed;
1251  // displacement - we loop through 3 times, generating above, online then below line placements successively
1252  for ( int i = 0; i <= 2; ++i )
1253  {
1254  LabelPosition *p = nullptr;
1255  if ( i == 0 && ( ( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1256  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1257  if ( i == 1 && flags & FLAG_ON_LINE )
1258  {
1259  p = _createCurvedCandidate( slp, angle_avg, 0 );
1260  p->setCost( p->cost() + 0.002 );
1261  }
1262  if ( i == 2 && ( ( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1263  {
1264  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1265  p->setCost( p->cost() + 0.001 );
1266  }
1267 
1268  if ( p && mLF->permissibleZonePrepared() )
1269  {
1270  bool within = true;
1271  LabelPosition *currentPos = p;
1272  while ( within && currentPos )
1273  {
1274  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1275  currentPos = currentPos->getNextPart();
1276  }
1277  if ( !within )
1278  {
1279  delete p;
1280  p = nullptr;
1281  }
1282  }
1283 
1284  if ( p )
1285  positions.append( p );
1286  }
1287 
1288  // delete original candidate
1289  delete slp;
1290  }
1291 
1292  int nbp = positions.size();
1293  for ( int i = 0; i < nbp; i++ )
1294  {
1295  lPos << positions.takeFirst();
1296  }
1297 
1298  delete[] path_distances;
1299 
1300  return nbp;
1301 }
1302 
1303 /*
1304  * seg 2
1305  * pt3 ____________pt2
1306  * ¦ ¦
1307  * ¦ ¦
1308  * seg 3 ¦ BBOX ¦ seg 1
1309  * ¦ ¦
1310  * ¦____________¦
1311  * pt0 seg 0 pt1
1312  *
1313  */
1314 
1315 int FeaturePart::createCandidatesForPolygon( QList< LabelPosition *> &lPos, PointSet *mapShape )
1316 {
1317  int i;
1318  int j;
1319 
1320  double labelWidth = getLabelWidth();
1321  double labelHeight = getLabelHeight();
1322 
1323  QLinkedList<PointSet *> shapes_toProcess;
1324  QLinkedList<PointSet *> shapes_final;
1325 
1326  mapShape->parent = nullptr;
1327 
1328  shapes_toProcess.append( mapShape );
1329 
1330  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1331 
1332  int nbp;
1333 
1334  if ( !shapes_final.isEmpty() )
1335  {
1336  QLinkedList<LabelPosition *> positions;
1337 
1338  int id = 0; // ids for candidates
1339  double dlx, dly; // delta from label center and bottom-left corner
1340  double alpha = 0.0; // rotation for the label
1341  double px, py;
1342  double dx;
1343  double dy;
1344  int bbid;
1345  double beta;
1346  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1347  double rx, ry;
1348  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1349  j = 0;
1350 
1351  // Compute bounding box foreach finalShape
1352  while ( !shapes_final.isEmpty() )
1353  {
1354  PointSet *shape = shapes_final.takeFirst();
1355  boxes[j] = shape->compute_chull_bbox();
1356 
1357  if ( shape->parent )
1358  delete shape;
1359 
1360  j++;
1361  }
1362 
1363  //dx = dy = min( yrm, xrm ) / 2;
1364  dx = labelWidth / 2.0;
1365  dy = labelHeight / 2.0;
1366 
1367  int numTry = 0;
1368 
1369  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1370  //then use a smaller limit for number of iterations
1371  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1372 
1373  do
1374  {
1375  for ( bbid = 0; bbid < j; bbid++ )
1376  {
1377  CHullBox *box = boxes[bbid];
1378 
1379  if ( ( box->length * box->width ) > ( xmax - xmin ) * ( ymax - ymin ) * 5 )
1380  {
1381  // Very Large BBOX (should never occur)
1382  continue;
1383  }
1384 
1386  {
1387  //check width/height of bbox is sufficient for label
1388  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1389  mLF->permissibleZone().boundingBox().height() < labelHeight )
1390  {
1391  //no way label can fit in this box, skip it
1392  continue;
1393  }
1394  }
1395 
1396  bool enoughPlace = false;
1398  {
1399  enoughPlace = true;
1400  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1401  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1402  int i, j;
1403 
1404  // Virtual label: center on bbox center, label size = 2x original size
1405  // alpha = 0.
1406  // If all corner are in bbox then place candidates horizontaly
1407  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1408  {
1409  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1410  {
1411  if ( !mapShape->containsPoint( rx, ry ) )
1412  {
1413  enoughPlace = false;
1414  break;
1415  }
1416  }
1417  if ( !enoughPlace )
1418  {
1419  break;
1420  }
1421  }
1422 
1423  } // arrangement== FREE ?
1424 
1425  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1426  {
1427  alpha = 0.0; // HORIZ
1428  }
1429  else if ( box->length > 1.5 * labelWidth && box->width > 1.5 * labelWidth )
1430  {
1431  if ( box->alpha <= M_PI / 4 )
1432  {
1433  alpha = box->alpha;
1434  }
1435  else
1436  {
1437  alpha = box->alpha - M_PI / 2;
1438  }
1439  }
1440  else if ( box->length > box->width )
1441  {
1442  alpha = box->alpha - M_PI / 2;
1443  }
1444  else
1445  {
1446  alpha = box->alpha;
1447  }
1448 
1449  beta = atan2( labelHeight, labelWidth ) + alpha;
1450 
1451 
1452  //alpha = box->alpha;
1453 
1454  // delta from label center and down-left corner
1455  dlx = cos( beta ) * diago;
1456  dly = sin( beta ) * diago;
1457 
1458  double px0, py0;
1459 
1460  px0 = box->width / 2.0;
1461  py0 = box->length / 2.0;
1462 
1463  px0 -= ceil( px0 / dx ) * dx;
1464  py0 -= ceil( py0 / dy ) * dy;
1465 
1466  for ( px = px0; px <= box->width; px += dx )
1467  {
1468  for ( py = py0; py <= box->length; py += dy )
1469  {
1470 
1471  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1472  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1473 
1474  rx += box->x[0];
1475  ry += box->y[0];
1476 
1477  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1478  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1479  : mapShape->containsPoint( rx, ry ) );
1480  if ( candidateAcceptable )
1481  {
1482  // cost is set to minimal value, evaluated later
1483  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1484  }
1485  }
1486  }
1487  } // forall box
1488 
1489  nbp = positions.size();
1490  if ( nbp == 0 )
1491  {
1492  dx /= 2;
1493  dy /= 2;
1494  numTry++;
1495  }
1496  }
1497  while ( nbp == 0 && numTry < maxTry );
1498 
1499  nbp = positions.size();
1500 
1501  for ( i = 0; i < nbp; i++ )
1502  {
1503  lPos << positions.takeFirst();
1504  }
1505 
1506  for ( bbid = 0; bbid < j; bbid++ )
1507  {
1508  delete boxes[bbid];
1509  }
1510 
1511  delete[] boxes;
1512  }
1513  else
1514  {
1515  nbp = 0;
1516  }
1517 
1518  return nbp;
1519 }
1520 
1521 int FeaturePart::createCandidates( QList< LabelPosition *> &lPos,
1522  double bboxMin[2], double bboxMax[2],
1523  PointSet *mapShape, RTree<LabelPosition *, double, 2, double> *candidates )
1524 {
1525  double bbox[4];
1526 
1527  bbox[0] = bboxMin[0];
1528  bbox[1] = bboxMin[1];
1529  bbox[2] = bboxMax[0];
1530  bbox[3] = bboxMax[1];
1531 
1532  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1533 
1534  if ( mLF->hasFixedPosition() )
1535  {
1536  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1537  }
1538  else
1539  {
1540  switch ( type )
1541  {
1542  case GEOS_POINT:
1544  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1546  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1547  else
1548  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1549  break;
1550  case GEOS_LINESTRING:
1551  if ( mLF->layer()->isCurved() )
1552  createCurvedCandidatesAlongLine( lPos, mapShape );
1553  else
1554  createCandidatesAlongLine( lPos, mapShape );
1555  break;
1556 
1557  case GEOS_POLYGON:
1558  switch ( mLF->layer()->arrangement() )
1559  {
1562  double cx, cy;
1563  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1565  createCandidatesOverPoint( cx, cy, lPos, angle );
1566  else
1567  createCandidatesAroundPoint( cx, cy, lPos, angle );
1568  break;
1570  createCandidatesAlongLine( lPos, mapShape );
1571  break;
1573  createCurvedCandidatesAlongLine( lPos, mapShape );
1574  break;
1575  default:
1576  createCandidatesForPolygon( lPos, mapShape );
1577  break;
1578  }
1579  }
1580  }
1581 
1582  // purge candidates that are outside the bbox
1583 
1584  QMutableListIterator< LabelPosition *> i( lPos );
1585  while ( i.hasNext() )
1586  {
1587  LabelPosition *pos = i.next();
1588  bool outside = false;
1589  if ( mLF->layer()->pal->getShowPartial() )
1590  outside = !pos->isIntersect( bbox );
1591  else
1592  outside = !pos->isInside( bbox );
1593  if ( outside )
1594  {
1595  i.remove();
1596  delete pos;
1597  }
1598  else // this one is OK
1599  {
1600  pos->insertIntoIndex( candidates );
1601  }
1602  }
1603 
1604  std::sort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1605  return lPos.count();
1606 }
1607 
1608 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4] )
1609 {
1610  if ( !mGeos )
1611  createGeosGeom();
1612 
1613  GEOSContextHandle_t ctxt = geosContext();
1614  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1615 
1616  double sizeCost = 0;
1617  if ( geomType == GEOS_LINESTRING )
1618  {
1619  double length;
1620  try
1621  {
1622  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1623  return; // failed to calculate length
1624  }
1625  catch ( GEOSException &e )
1626  {
1627  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1628  return;
1629  }
1630  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1631  if ( length >= bbox_length / 4 )
1632  return; // the line is longer than quarter of height or width - don't penalize it
1633 
1634  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1635  }
1636  else if ( geomType == GEOS_POLYGON )
1637  {
1638  double area;
1639  try
1640  {
1641  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1642  return;
1643  }
1644  catch ( GEOSException &e )
1645  {
1646  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1647  return;
1648  }
1649  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1650  if ( area >= bbox_area / 16 )
1651  return; // covers more than 1/16 of our view - don't penalize it
1652 
1653  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1654  }
1655  else
1656  return; // no size penalty for points
1657 
1658  // apply the penalty
1659  for ( int i = 0; i < nbp; i++ )
1660  {
1661  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1662  }
1663 }
1664 
1666 {
1667  if ( !p2->mGeos )
1668  p2->createGeosGeom();
1669 
1670  try
1671  {
1672  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1673  }
1674  catch ( GEOSException &e )
1675  {
1676  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1677  return false;
1678  }
1679 }
1680 
1682 {
1683  if ( !mGeos )
1684  createGeosGeom();
1685  if ( !other->mGeos )
1686  other->createGeosGeom();
1687 
1688  GEOSContextHandle_t ctxt = geosContext();
1689  try
1690  {
1691  GEOSGeometry *g1 = GEOSGeom_clone_r( ctxt, mGeos );
1692  GEOSGeometry *g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1693  GEOSGeometry *geoms[2] = { g1, g2 };
1694  GEOSGeometry *g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1695  GEOSGeometry *gTmp = GEOSLineMerge_r( ctxt, g );
1696  GEOSGeom_destroy_r( ctxt, g );
1697 
1698  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1699  {
1700  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1701  GEOSGeom_destroy_r( ctxt, gTmp );
1702  return false;
1703  }
1704  invalidateGeos();
1705 
1706  // set up new geometry
1707  mGeos = gTmp;
1708  mOwnsGeom = true;
1709 
1710  deleteCoords();
1711  qDeleteAll( mHoles );
1712  mHoles.clear();
1713  extractCoords( mGeos );
1714  return true;
1715  }
1716  catch ( GEOSException &e )
1717  {
1718  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1719  return false;
1720  }
1721 }
1722 
1724 {
1725  if ( mLF->alwaysShow() )
1726  {
1727  //if feature is set to always show, bump the priority up by orders of magnitude
1728  //so that other feature's labels are unlikely to be placed over the label for this feature
1729  //(negative numbers due to how pal::extract calculates inactive cost)
1730  return -0.2;
1731  }
1732 
1733  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1734 }
1735 
1737 {
1738  bool uprightLabel = false;
1739 
1740  switch ( mLF->layer()->upsidedownLabels() )
1741  {
1742  case Layer::Upright:
1743  uprightLabel = true;
1744  break;
1745  case Layer::ShowDefined:
1746  // upright only dynamic labels
1747  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
1748  {
1749  uprightLabel = true;
1750  }
1751  break;
1752  case Layer::ShowAll:
1753  break;
1754  default:
1755  uprightLabel = true;
1756  }
1757  return uprightLabel;
1758 }
1759 
1760 bool FeaturePart::nextCharPosition( double charWidth, double segment_length, PointSet *path_positions, int &index, double &distance,
1761  double &start_x, double &start_y, double &end_x, double &end_y ) const
1762 {
1763  // Coordinates this character will start at
1764  if ( qgsDoubleNear( segment_length, 0.0 ) )
1765  {
1766  // Not allowed to place across on 0 length segments or discontinuities
1767  return false;
1768  }
1769 
1770  double old_x = path_positions->x[index - 1];
1771  double old_y = path_positions->y[index - 1];
1772 
1773  double new_x = path_positions->x[index];
1774  double new_y = path_positions->y[index];
1775 
1776  double dx = new_x - old_x;
1777  double dy = new_y - old_y;
1778 
1779  start_x = old_x + dx * distance / segment_length;
1780  start_y = old_y + dy * distance / segment_length;
1781 
1782  // Coordinates this character ends at, calculated below
1783  end_x = 0;
1784  end_y = 0;
1785 
1786  if ( segment_length - distance >= charWidth )
1787  {
1788  // if the distance remaining in this segment is enough, we just go further along the segment
1789  distance += charWidth;
1790  end_x = old_x + dx * distance / segment_length;
1791  end_y = old_y + dy * distance / segment_length;
1792  }
1793  else
1794  {
1795  // If there isn't enough distance left on this segment
1796  // then we need to search until we find the line segment that ends further than ci.width away
1797  do
1798  {
1799  old_x = new_x;
1800  old_y = new_y;
1801  index++;
1802  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1803  {
1804  return false;
1805  }
1806  new_x = path_positions->x[index];
1807  new_y = path_positions->y[index];
1808  dx = new_x - old_x;
1809  dy = new_y - old_y;
1810  }
1811  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_
1812 
1813  // Calculate the position to place the end of the character on
1814  GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );
1815 
1816  // Need to calculate distance on the new segment
1817  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1818  }
1819  return true;
1820 }
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:1521
Label below point, slightly right of center.
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:220
double length
Definition: pointset.h:57
Label on bottom-left of point.
virtual ~FeaturePart()
Delete the feature.
Definition: feature.cpp:81
int createCandidatesForPolygon(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1315
void invalidateGeos()
Definition: pointset.cpp:204
double distLabel() const
Applies to "around point" placement strategy or linestring features.
bool isIntersect(double *bbox)
Is the labelposition intersect the bounding-box ?
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:268
double fixedAngle() const
Returns the fixed angle for the feature&#39;s label.
Definition: feature.h:234
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:155
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
static bool candidateSortGrow(const LabelPosition *c1, const LabelPosition *c2)
Sorts label candidates in ascending order of cost.
double max_char_angle_outside
Definition: feature.h:76
double priority() const
Returns the feature&#39;s labeling priority.
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
Label on top-left of point.
void setCost(double newCost)
Sets the candidate label position&#39;s geographical cost.
int incrementUpsideDownCharCount()
Increases the count of upside down characters for this label position.
bool hasFixedRotation() const
Returns true if the feature&#39;s label has a fixed rotation.
Definition: feature.h:231
double getY(int i=0) const
get the down-left y coordinate
double y
Definition: qgspointxy.h:47
A set of features which influence the labeling process.
Definition: layer.h:60
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode...
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
int createCandidatesAlongLineNearStraightSegments(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for line feature, by trying to place candidates towards the middle of the longest...
Definition: feature.cpp:602
void createGeosGeom() const
Definition: pointset.cpp:150
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
friend class LabelPosition
Definition: pointset.h:68
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label&#39;s permissibleZone().
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.
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1681
UpsideDownLabels upsidedownLabels() const
Returns how upside down labels are handled within the layer.
Definition: layer.h:206
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1665
const QgsMargins & visualMargin() const
Returns the visual margin for the label feature.
double getLabelDistance() const
Definition: feature.h:228
int createCandidatesAtOrderedPositionsOverPoint(double x, double y, QList< LabelPosition *> &lPos, double angle)
Generates candidates following a prioritized list of predefined positions around a point...
Definition: feature.cpp:310
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:203
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cc:857
QgsPointXY positionOffset() const
Applies only to "offset from point" placement strategy.
#define M_PI_2
Definition: util.cpp:45
Arranges candidates following the curvature of a line feature. Applies to line layers only...
CharacterInfo * char_info
Definition: feature.h:79
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
double width
Definition: pointset.h:56
int createCandidatesAroundPoint(double x, double y, QList< LabelPosition *> &lPos, double angle)
Generate candidates for point feature, located around a specified point.
Definition: feature.cpp:436
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:173
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int &orientation, int index, double distance, bool &reversed, bool &flip)
Returns the label position for a curved label at a specific offset along a path.
Definition: feature.cpp:969
double cost() const
Returns the candidate label position&#39;s geographical cost.
pal::LabelInfo * curvedLabelInfo() const
Get additional infor required for curved label placement. Returns null if not set.
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Label on left of point.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
QgsGeometry permissibleZone() const
Returns the label&#39;s permissible zone geometry.
bool nextCharPosition(double charWidth, double segment_length, PointSet *path_positions, int &index, double &distance, double &start_x, double &start_y, double &end_x, double &end_y) const
Returns true if the next char position is found. The referenced parameters are updated.
Definition: feature.cpp:1760
bool getShowPartial()
Get flag show partial label.
Definition: pal.cpp:624
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py)
Get a point a set distance along a line geometry.
Definition: pointset.cpp:811
void addSizePenalty(int nbp, QList< LabelPosition *> &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1608
PointSet * parent
Definition: pointset.h:163
double * x
Definition: pointset.h:154
double ymax
Definition: pointset.h:177
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
double xmin
Definition: pointset.h:174
double getHeight() const
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:118
PointSet * holeOf
Definition: pointset.h:162
double ymin
Definition: pointset.h:176
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
LabelPosition * getNextPart() const
Optional additional info about label (for curved labels)
Definition: feature.h:51
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:150
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1723
QgsPalLayerSettings::Placement arrangement() const
Returns the layer&#39;s arrangement policy.
Definition: layer.h:96
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
double label_height
Definition: feature.h:77
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:63
pal::Layer * layer() const
Get PAL layer of the label feature. Should be only used internally in PAL.
int createCandidatesAlongLine(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for line feature.
Definition: feature.cpp:589
GEOSContextHandle_t geosContext()
Get GEOS context handle to be used in all GEOS library calls with reentrant API.
Definition: pal.cpp:48
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
double getLabelHeight() const
Definition: feature.h:227
void deleteCoords()
Definition: pointset.cpp:232
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
Main class to handle feature.
Definition: feature.h:92
int upsideDownCharCount() const
Returns the number of upside down characters for this label position.
Offset distance applies from rendered symbol bounds.
void setPartId(int id)
double x
Definition: qgspointxy.h:46
bool hasFixedPosition() const
Returns true if the feature&#39;s label has a fixed position.
Definition: feature.h:237
int createCandidatesOverPoint(double x, double y, QList< LabelPosition *> &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:231
double * y
Definition: pointset.h:155
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
Pal * pal
Definition: layer.h:254
void setNextPart(LabelPosition *next)
static void logMessage(const QString &message, const QString &tag=QString(), MessageLevel level=QgsMessageLog::WARNING)
add a message to the instance (and create it if necessary)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:572
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
double x[4]
Definition: pointset.h:51
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:160
double getAlpha() const
get alpha
double length() const
Returns length of line geometry.
Definition: pointset.cpp:858
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
QList< FeaturePart * > mHoles
Definition: feature.h:284
double getWidth() const
double getX(int i=0) const
get the down-left x coordinate
double y[4]
Definition: pointset.h:52
double max_char_angle_inside
Definition: feature.h:75
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
int createCandidatesAlongLineNearMidpoint(QList< LabelPosition *> &lPos, PointSet *mapShape, double initialCost=0.0)
Generate candidates for line feature, by trying to place candidates as close as possible to the line&#39;...
Definition: feature.cpp:822
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:89
QString name() const
Returns the layer&#39;s name.
Definition: layer.h:91
double getLabelWidth() const
Definition: feature.h:226
GEOSGeometry * mGeos
Definition: pointset.h:150
LabelPosition is a candidate feature label position.
Definition: labelposition.h:52
QgsLabelFeature * mLF
Definition: feature.h:283
Label on top of point, slightly left of center.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:62
Label on right of point.
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:192
LineArrangementFlags arrangementFlags() const
Returns the layer&#39;s arrangement flags.
Definition: layer.h:111
double alpha
Definition: pointset.h:54
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...
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
Definition: feature.cpp:53
QgsPointXY fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
#define M_PI
Definition: feature.cpp:48
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:769
static void splitPolygons(QLinkedList< PointSet *> &shapes_toProcess, QLinkedList< PointSet *> &shapes_final, double xrm, double yrm)
Split a concave shape into several convex shapes.
Definition: pointset.cpp:295
double xmax
Definition: pointset.h:175
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
int char_num
Definition: feature.h:78
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:100
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1736
bool mOwnsGeom
Definition: pointset.h:151
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:37
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:125
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
int createCurvedCandidatesAlongLine(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:1147
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...