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