QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 = QgsGeos::getGEOSHandler();
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 = std::numeric_limits<double>::max();
126  xmax = ymax = std::numeric_limits<double>::lowest();
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->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  if ( belowLine )
775  {
776  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
777  {
778  const double candidateCost = cost + ( reversed ? 0 : 0.001 );
779  lPos.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
780  }
781  }
782  if ( aboveLine )
783  {
784  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
785  {
786  const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements
787  lPos.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
788  }
789  }
790  if ( flags & FLAG_ON_LINE )
791  {
792  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
793  {
794  const double candidateCost = cost + 0.002;
795  lPos.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
796  }
797  }
798  }
800  {
801  lPos.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
802  }
803  else
804  {
805  // an invalid arrangement?
806  }
807 
808  currentDistanceAlongLine += lineStepDistance;
809  }
810  }
811 
812  delete[] segmentLengths;
813  delete[] distanceToSegment;
814  return lPos.size();
815 }
816 
817 int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition *> &lPos, PointSet *mapShape, double initialCost )
818 {
819  double distanceLineToLabel = getLabelDistance();
820 
821  double labelWidth = getLabelWidth();
822  double labelHeight = getLabelHeight();
823 
824  double angle;
825  double cost;
826 
827  LineArrangementFlags flags = mLF->arrangementFlags();
828  if ( flags == 0 )
829  flags = FLAG_ON_LINE; // default flag
830 
831  QList<LabelPosition *> positions;
832 
833  PointSet *line = mapShape;
834  int nbPoints = line->nbPoints;
835  double *x = line->x;
836  double *y = line->y;
837 
838  double *segmentLengths = new double[nbPoints - 1]; // segments lengths distance bw pt[i] && pt[i+1]
839  double *distanceToSegment = new double[nbPoints]; // absolute distance bw pt[0] and pt[i] along the line
840 
841  double totalLineLength = 0.0; // line length
842  for ( int i = 0; i < line->nbPoints - 1; i++ )
843  {
844  if ( i == 0 )
845  distanceToSegment[i] = 0;
846  else
847  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
848 
849  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
850  totalLineLength += segmentLengths[i];
851  }
852  distanceToSegment[line->nbPoints - 1] = totalLineLength;
853 
854  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
855  double currentDistanceAlongLine = 0;
856 
857  if ( totalLineLength > labelWidth )
858  {
859  lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
860  }
861  else if ( !line->isClosed() ) // line length < label width => centering label position
862  {
863  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
864  lineStepDistance = -1;
865  totalLineLength = labelWidth;
866  }
867  else
868  {
869  // closed line, not long enough for label => no candidates!
870  currentDistanceAlongLine = std::numeric_limits< double >::max();
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 = std::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 = std::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 = std::fabs( 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 = std::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 + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
929  {
930  const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements
931  positions.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
932  }
933  }
934  if ( belowLine )
935  {
936  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
937  {
938  const double candidateCost = cost + ( !reversed ? 0.001 : 0 );
939  positions.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
940  }
941  }
942  if ( flags & FLAG_ON_LINE )
943  {
944  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
945  {
946  const double candidateCost = cost + 0.002;
947  positions.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line
948  }
949  }
950  }
952  {
953  positions.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
954  }
955  else
956  {
957  // an invalid arrangement?
958  }
959 
960  currentDistanceAlongLine += lineStepDistance;
961 
962  i++;
963 
964  if ( lineStepDistance < 0 )
965  break;
966  }
967 
968  //delete line;
969 
970  delete[] segmentLengths;
971  delete[] distanceToSegment;
972 
973  lPos.append( positions );
974  return lPos.size();
975 }
976 
977 
978 LabelPosition *FeaturePart::curvedPlacementAtOffset( PointSet *path_positions, double *path_distances, int &orientation, const double offsetAlongLine, bool &reversed, bool &flip )
979 {
980  double offsetAlongSegment = offsetAlongLine;
981  int index = 1;
982  // Find index of segment corresponding to starting offset
983  while ( index < path_positions->nbPoints && offsetAlongSegment > path_distances[index] )
984  {
985  offsetAlongSegment -= path_distances[index];
986  index += 1;
987  }
988  if ( index >= path_positions->nbPoints )
989  {
990  return nullptr;
991  }
992 
993  LabelInfo *li = mLF->curvedLabelInfo();
994 
995  double string_height = li->label_height;
996 
997  const double segment_length = path_distances[index];
998  if ( qgsDoubleNear( segment_length, 0.0 ) )
999  {
1000  // Not allowed to place across on 0 length segments or discontinuities
1001  return nullptr;
1002  }
1003 
1004  if ( orientation == 0 ) // Must be map orientation
1005  {
1006  // Calculate the orientation based on the angle of the path segment under consideration
1007 
1008  double _distance = offsetAlongSegment;
1009  int endindex = index;
1010 
1011  double startLabelX = 0;
1012  double startLabelY = 0;
1013  double endLabelX = 0;
1014  double endLabelY = 0;
1015  for ( int i = 0; i < li->char_num; i++ )
1016  {
1017  LabelInfo::CharacterInfo &ci = li->char_info[i];
1018  double characterStartX, characterStartY;
1019  if ( !nextCharPosition( ci.width, path_distances[endindex], path_positions, endindex, _distance, characterStartX, characterStartY, endLabelX, endLabelY ) )
1020  {
1021  return nullptr;
1022  }
1023  if ( i == 0 )
1024  {
1025  startLabelX = characterStartX;
1026  startLabelY = characterStartY;
1027  }
1028  }
1029 
1030  // Determine the angle of the path segment under consideration
1031  double dx = endLabelX - startLabelX;
1032  double dy = endLabelY - startLabelY;
1033  const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
1034 
1035  bool isRightToLeft = ( lineAngle > 90 || lineAngle < -90 );
1036  reversed = isRightToLeft;
1037  orientation = isRightToLeft ? -1 : 1;
1038  }
1039 
1040  if ( !showUprightLabels() )
1041  {
1042  if ( orientation < 0 )
1043  {
1044  flip = true; // Report to the caller, that the orientation is flipped
1045  reversed = !reversed;
1046  orientation = 1;
1047  }
1048  }
1049 
1050  LabelPosition *slp = nullptr;
1051  LabelPosition *slp_tmp = nullptr;
1052 
1053  double old_x = path_positions->x[index - 1];
1054  double old_y = path_positions->y[index - 1];
1055 
1056  double new_x = path_positions->x[index];
1057  double new_y = path_positions->y[index];
1058 
1059  double dx = new_x - old_x;
1060  double dy = new_y - old_y;
1061 
1062  double angle = std::atan2( -dy, dx );
1063 
1064  for ( int i = 0; i < li->char_num; i++ )
1065  {
1066  double last_character_angle = angle;
1067 
1068  // grab the next character according to the orientation
1069  LabelInfo::CharacterInfo &ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num - i - 1] );
1070  if ( qgsDoubleNear( ci.width, 0.0 ) )
1071  // Certain scripts rely on zero-width character, skip those to prevent failure (see #15801)
1072  continue;
1073 
1074  double start_x, start_y, end_x, end_y;
1075  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, index, offsetAlongSegment, start_x, start_y, end_x, end_y ) )
1076  {
1077  delete slp;
1078  return nullptr;
1079  }
1080 
1081  // Calculate angle from the start of the character to the end based on start_/end_ position
1082  angle = std::atan2( start_y - end_y, end_x - start_x );
1083 
1084  // Test last_character_angle vs angle
1085  // since our rendering angle has changed then check against our
1086  // max allowable angle change.
1087  double angle_delta = last_character_angle - angle;
1088  // normalise between -180 and 180
1089  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1090  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1091  if ( ( li->max_char_angle_inside > 0 && angle_delta > 0
1092  && angle_delta > li->max_char_angle_inside * ( M_PI / 180 ) )
1093  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1094  && angle_delta < li->max_char_angle_outside * ( M_PI / 180 ) ) )
1095  {
1096  delete slp;
1097  return nullptr;
1098  }
1099 
1100  // Shift the character downwards since the draw position is specified at the baseline
1101  // and we're calculating the mean line here
1102  double dist = 0.9 * li->label_height / 2;
1103  if ( orientation < 0 )
1104  {
1105  dist = -dist;
1106  flip = true;
1107  }
1108  start_x += dist * std::cos( angle + M_PI_2 );
1109  start_y -= dist * std::sin( angle + M_PI_2 );
1110 
1111  double render_angle = angle;
1112 
1113  double render_x = start_x;
1114  double render_y = start_y;
1115 
1116  // Center the text on the line
1117  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1118  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1119 
1120  if ( orientation < 0 )
1121  {
1122  // rotate in place
1123  render_x += ci.width * std::cos( render_angle ); //- (string_height-2)*sin(render_angle);
1124  render_y -= ci.width * std::sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1125  render_angle += M_PI;
1126  }
1127 
1128  LabelPosition *tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1129  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1130  if ( !slp )
1131  slp = tmp;
1132  else
1133  slp_tmp->setNextPart( tmp );
1134  slp_tmp = tmp;
1135 
1136  // Normalise to 0 <= angle < 2PI
1137  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1138  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1139 
1140  if ( render_angle > M_PI_2 && render_angle < 1.5 * M_PI )
1142  }
1143 
1144  return slp;
1145 }
1146 
1147 static LabelPosition *_createCurvedCandidate( LabelPosition *lp, double angle, double dist )
1148 {
1149  LabelPosition *newLp = new LabelPosition( *lp );
1150  newLp->offsetPosition( dist * std::cos( angle + M_PI_2 ), dist * std::sin( angle + M_PI_2 ) );
1151  return newLp;
1152 }
1153 
1154 int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition * > &lPos, PointSet *mapShape )
1155 {
1156  LabelInfo *li = mLF->curvedLabelInfo();
1157 
1158  // label info must be present
1159  if ( !li || li->char_num == 0 )
1160  return 0;
1161 
1162  // distance calculation
1163  std::unique_ptr< double [] > path_distances = qgis::make_unique<double[]>( mapShape->nbPoints );
1164  double total_distance = 0;
1165  double old_x = -1.0, old_y = -1.0;
1166  for ( int i = 0; i < mapShape->nbPoints; i++ )
1167  {
1168  if ( i == 0 )
1169  path_distances[i] = 0;
1170  else
1171  path_distances[i] = std::sqrt( std::pow( old_x - mapShape->x[i], 2 ) + std::pow( old_y - mapShape->y[i], 2 ) );
1172  old_x = mapShape->x[i];
1173  old_y = mapShape->y[i];
1174 
1175  total_distance += path_distances[i];
1176  }
1177 
1178  if ( qgsDoubleNear( total_distance, 0.0 ) )
1179  {
1180  return 0;
1181  }
1182 
1183  double totalCharacterWidth = 0;
1184  for ( int i = 0; i < li->char_num; ++i )
1185  totalCharacterWidth += li->char_info[ i ].width;
1186 
1187  if ( totalCharacterWidth > total_distance )
1188  {
1189  // label doesn't fit on this line, don't waste time trying to make candidates
1190  // TODO: in future allow this, and allow label to overlap end of line
1191  return 0;
1192  }
1193 
1194  QLinkedList<LabelPosition *> positions;
1195  double delta = std::max( li->label_height / 6, total_distance / mLF->layer()->pal->line_p );
1196 
1197  pal::LineArrangementFlags flags = mLF->arrangementFlags();
1198  if ( flags == 0 )
1199  flags = FLAG_ON_LINE; // default flag
1200 
1201  // generate curved labels
1202  for ( double distanceAlongLineToStartCandidate = 0; distanceAlongLineToStartCandidate < total_distance; distanceAlongLineToStartCandidate += delta )
1203  {
1204  bool flip = false;
1205  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1206  bool reversed = false;
1207 
1208  // an orientation of 0 means try both orientations and choose the best
1209  int orientation = 0;
1210  if ( !( flags & FLAG_MAP_ORIENTATION ) )
1211  {
1212  //... but if we are using line orientation flags, then we can only accept a single orientation,
1213  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1214  orientation = 1;
1215  }
1216 
1217  LabelPosition *slp = curvedPlacementAtOffset( mapShape, path_distances.get(), orientation, distanceAlongLineToStartCandidate, reversed, flip );
1218  if ( !slp )
1219  continue;
1220 
1221  // If we placed too many characters upside down
1222  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1223  {
1224  // if labels should be shown upright then retry with the opposite orientation
1225  if ( ( showUprightLabels() && !flip ) )
1226  {
1227  delete slp;
1228  orientation = -orientation;
1229  slp = curvedPlacementAtOffset( mapShape, path_distances.get(), orientation, distanceAlongLineToStartCandidate, reversed, flip );
1230  }
1231  }
1232  if ( !slp )
1233  continue;
1234 
1235  // evaluate cost
1236  double angle_diff = 0.0, angle_last = 0.0, diff;
1237  LabelPosition *tmp = slp;
1238  double sin_avg = 0, cos_avg = 0;
1239  while ( tmp )
1240  {
1241  if ( tmp != slp ) // not first?
1242  {
1243  diff = std::fabs( tmp->getAlpha() - angle_last );
1244  if ( diff > 2 * M_PI ) diff -= 2 * M_PI;
1245  diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1246  angle_diff += diff;
1247  }
1248 
1249  sin_avg += std::sin( tmp->getAlpha() );
1250  cos_avg += std::cos( tmp->getAlpha() );
1251  angle_last = tmp->getAlpha();
1252  tmp = tmp->getNextPart();
1253  }
1254 
1255  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1256  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1257  if ( cost < 0.0001 ) cost = 0.0001;
1258 
1259  // penalize positions which are further from the line's midpoint
1260  double labelCenter = distanceAlongLineToStartCandidate + getLabelWidth() / 2;
1261  double costCenter = std::fabs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1262  cost += costCenter / 100; // < 0, 0.005 >
1263  slp->setCost( cost );
1264 
1265  // average angle is calculated with respect to periodicity of angles
1266  double angle_avg = std::atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1267  bool localreversed = flip ? !reversed : reversed;
1268  // displacement - we loop through 3 times, generating above, online then below line placements successively
1269  for ( int i = 0; i <= 2; ++i )
1270  {
1271  LabelPosition *p = nullptr;
1272  if ( i == 0 && ( ( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1273  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1274  if ( i == 1 && flags & FLAG_ON_LINE )
1275  {
1276  p = _createCurvedCandidate( slp, angle_avg, 0 );
1277  p->setCost( p->cost() + 0.002 );
1278  }
1279  if ( i == 2 && ( ( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1280  {
1281  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1282  p->setCost( p->cost() + 0.001 );
1283  }
1284 
1285  if ( p && mLF->permissibleZonePrepared() )
1286  {
1287  bool within = true;
1288  LabelPosition *currentPos = p;
1289  while ( within && currentPos )
1290  {
1291  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1292  currentPos = currentPos->getNextPart();
1293  }
1294  if ( !within )
1295  {
1296  delete p;
1297  p = nullptr;
1298  }
1299  }
1300 
1301  if ( p )
1302  positions.append( p );
1303  }
1304 
1305  // delete original candidate
1306  delete slp;
1307  }
1308 
1309  int nbp = positions.size();
1310  for ( int i = 0; i < nbp; i++ )
1311  {
1312  lPos << positions.takeFirst();
1313  }
1314 
1315  return nbp;
1316 }
1317 
1318 /*
1319  * seg 2
1320  * pt3 ____________pt2
1321  * ¦ ¦
1322  * ¦ ¦
1323  * seg 3 ¦ BBOX ¦ seg 1
1324  * ¦ ¦
1325  * ¦____________¦
1326  * pt0 seg 0 pt1
1327  *
1328  */
1329 
1330 int FeaturePart::createCandidatesForPolygon( QList< LabelPosition *> &lPos, PointSet *mapShape )
1331 {
1332  int i;
1333  int j;
1334 
1335  double labelWidth = getLabelWidth();
1336  double labelHeight = getLabelHeight();
1337 
1338  QLinkedList<PointSet *> shapes_toProcess;
1339  QLinkedList<PointSet *> shapes_final;
1340 
1341  mapShape->parent = nullptr;
1342 
1343  shapes_toProcess.append( mapShape );
1344 
1345  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1346 
1347  int nbp;
1348 
1349  if ( !shapes_final.isEmpty() )
1350  {
1351  QLinkedList<LabelPosition *> positions;
1352 
1353  int id = 0; // ids for candidates
1354  double dlx, dly; // delta from label center and bottom-left corner
1355  double alpha = 0.0; // rotation for the label
1356  double px, py;
1357  double dx;
1358  double dy;
1359  int bbid;
1360  double beta;
1361  double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1362  double rx, ry;
1363  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1364  j = 0;
1365 
1366  // Compute bounding box foreach finalShape
1367  while ( !shapes_final.isEmpty() )
1368  {
1369  PointSet *shape = shapes_final.takeFirst();
1370  boxes[j] = shape->compute_chull_bbox();
1371 
1372  if ( shape->parent )
1373  delete shape;
1374 
1375  j++;
1376  }
1377 
1378  //dx = dy = min( yrm, xrm ) / 2;
1379  dx = labelWidth / 2.0;
1380  dy = labelHeight / 2.0;
1381 
1382  int numTry = 0;
1383 
1384  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1385  //then use a smaller limit for number of iterations
1386  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1387 
1388  do
1389  {
1390  for ( bbid = 0; bbid < j; bbid++ )
1391  {
1392  CHullBox *box = boxes[bbid];
1393 
1394  if ( ( box->length * box->width ) > ( xmax - xmin ) * ( ymax - ymin ) * 5 )
1395  {
1396  // Very Large BBOX (should never occur)
1397  continue;
1398  }
1399 
1401  {
1402  //check width/height of bbox is sufficient for label
1403  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1404  mLF->permissibleZone().boundingBox().height() < labelHeight )
1405  {
1406  //no way label can fit in this box, skip it
1407  continue;
1408  }
1409  }
1410 
1411  bool enoughPlace = false;
1413  {
1414  enoughPlace = true;
1415  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1416  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1417  int i, j;
1418 
1419  // Virtual label: center on bbox center, label size = 2x original size
1420  // alpha = 0.
1421  // If all corner are in bbox then place candidates horizontaly
1422  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1423  {
1424  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1425  {
1426  if ( !mapShape->containsPoint( rx, ry ) )
1427  {
1428  enoughPlace = false;
1429  break;
1430  }
1431  }
1432  if ( !enoughPlace )
1433  {
1434  break;
1435  }
1436  }
1437 
1438  } // arrangement== FREE ?
1439 
1440  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1441  {
1442  alpha = 0.0; // HORIZ
1443  }
1444  else if ( box->length > 1.5 * labelWidth && box->width > 1.5 * labelWidth )
1445  {
1446  if ( box->alpha <= M_PI_4 )
1447  {
1448  alpha = box->alpha;
1449  }
1450  else
1451  {
1452  alpha = box->alpha - M_PI_2;
1453  }
1454  }
1455  else if ( box->length > box->width )
1456  {
1457  alpha = box->alpha - M_PI_2;
1458  }
1459  else
1460  {
1461  alpha = box->alpha;
1462  }
1463 
1464  beta = std::atan2( labelHeight, labelWidth ) + alpha;
1465 
1466 
1467  //alpha = box->alpha;
1468 
1469  // delta from label center and down-left corner
1470  dlx = std::cos( beta ) * diago;
1471  dly = std::sin( beta ) * diago;
1472 
1473  double px0, py0;
1474 
1475  px0 = box->width / 2.0;
1476  py0 = box->length / 2.0;
1477 
1478  px0 -= std::ceil( px0 / dx ) * dx;
1479  py0 -= std::ceil( py0 / dy ) * dy;
1480 
1481  for ( px = px0; px <= box->width; px += dx )
1482  {
1483  for ( py = py0; py <= box->length; py += dy )
1484  {
1485 
1486  rx = std::cos( box->alpha ) * px + std::cos( box->alpha - M_PI_2 ) * py;
1487  ry = std::sin( box->alpha ) * px + std::sin( box->alpha - M_PI_2 ) * py;
1488 
1489  rx += box->x[0];
1490  ry += box->y[0];
1491 
1492  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1493  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1494  : mapShape->containsPoint( rx, ry ) );
1495  if ( candidateAcceptable )
1496  {
1497  // cost is set to minimal value, evaluated later
1498  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1499  }
1500  }
1501  }
1502  } // forall box
1503 
1504  nbp = positions.size();
1505  if ( nbp == 0 )
1506  {
1507  dx /= 2;
1508  dy /= 2;
1509  numTry++;
1510  }
1511  }
1512  while ( nbp == 0 && numTry < maxTry );
1513 
1514  nbp = positions.size();
1515 
1516  for ( i = 0; i < nbp; i++ )
1517  {
1518  lPos << positions.takeFirst();
1519  }
1520 
1521  for ( bbid = 0; bbid < j; bbid++ )
1522  {
1523  delete boxes[bbid];
1524  }
1525 
1526  delete[] boxes;
1527  }
1528  else
1529  {
1530  nbp = 0;
1531  }
1532 
1533  return nbp;
1534 }
1535 
1536 int FeaturePart::createCandidates( QList< LabelPosition *> &lPos,
1537  const GEOSPreparedGeometry *mapBoundary,
1538  PointSet *mapShape, RTree<LabelPosition *, double, 2, double> *candidates )
1539 {
1540  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1541 
1542  if ( mLF->hasFixedPosition() )
1543  {
1544  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1545  }
1546  else
1547  {
1548  switch ( type )
1549  {
1550  case GEOS_POINT:
1552  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1554  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1555  else
1556  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1557  break;
1558  case GEOS_LINESTRING:
1559  if ( mLF->layer()->isCurved() )
1560  createCurvedCandidatesAlongLine( lPos, mapShape );
1561  else
1562  createCandidatesAlongLine( lPos, mapShape );
1563  break;
1564 
1565  case GEOS_POLYGON:
1566  switch ( mLF->layer()->arrangement() )
1567  {
1570  double cx, cy;
1571  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1573  createCandidatesOverPoint( cx, cy, lPos, angle );
1574  else
1575  createCandidatesAroundPoint( cx, cy, lPos, angle );
1576  break;
1578  createCandidatesAlongLine( lPos, mapShape );
1579  break;
1581  createCurvedCandidatesAlongLine( lPos, mapShape );
1582  break;
1583  default:
1584  createCandidatesForPolygon( lPos, mapShape );
1585  break;
1586  }
1587  }
1588  }
1589 
1590  // purge candidates that are outside the bbox
1591 
1592  QMutableListIterator< LabelPosition *> i( lPos );
1593  while ( i.hasNext() )
1594  {
1595  LabelPosition *pos = i.next();
1596  bool outside = false;
1597 
1598  if ( mLF->layer()->pal->getShowPartial() )
1599  outside = !pos->intersects( mapBoundary );
1600  else
1601  outside = !pos->within( mapBoundary );
1602  if ( outside )
1603  {
1604  i.remove();
1605  delete pos;
1606  }
1607  else // this one is OK
1608  {
1609  pos->insertIntoIndex( candidates );
1610  }
1611  }
1612 
1613  std::sort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1614  return lPos.count();
1615 }
1616 
1617 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4] )
1618 {
1619  if ( !mGeos )
1620  createGeosGeom();
1621 
1622  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
1623  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1624 
1625  double sizeCost = 0;
1626  if ( geomType == GEOS_LINESTRING )
1627  {
1628  double length;
1629  try
1630  {
1631  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1632  return; // failed to calculate length
1633  }
1634  catch ( GEOSException &e )
1635  {
1636  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1637  return;
1638  }
1639  double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
1640  if ( length >= bbox_length / 4 )
1641  return; // the line is longer than quarter of height or width - don't penalize it
1642 
1643  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1644  }
1645  else if ( geomType == GEOS_POLYGON )
1646  {
1647  double area;
1648  try
1649  {
1650  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1651  return;
1652  }
1653  catch ( GEOSException &e )
1654  {
1655  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1656  return;
1657  }
1658  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1659  if ( area >= bbox_area / 16 )
1660  return; // covers more than 1/16 of our view - don't penalize it
1661 
1662  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1663  }
1664  else
1665  return; // no size penalty for points
1666 
1667  // apply the penalty
1668  for ( int i = 0; i < nbp; i++ )
1669  {
1670  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1671  }
1672 }
1673 
1675 {
1676  if ( !p2->mGeos )
1677  p2->createGeosGeom();
1678 
1679  try
1680  {
1681  return ( GEOSPreparedTouches_r( QgsGeos::getGEOSHandler(), preparedGeom(), p2->mGeos ) == 1 );
1682  }
1683  catch ( GEOSException &e )
1684  {
1685  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1686  return false;
1687  }
1688 }
1689 
1691 {
1692  if ( !mGeos )
1693  createGeosGeom();
1694  if ( !other->mGeos )
1695  other->createGeosGeom();
1696 
1697  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
1698  try
1699  {
1700  GEOSGeometry *g1 = GEOSGeom_clone_r( ctxt, mGeos );
1701  GEOSGeometry *g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1702  GEOSGeometry *geoms[2] = { g1, g2 };
1703  geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
1704  geos::unique_ptr gTmp( GEOSLineMerge_r( ctxt, g.get() ) );
1705 
1706  if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
1707  {
1708  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1709  return false;
1710  }
1711  invalidateGeos();
1712 
1713  // set up new geometry
1714  mGeos = gTmp.release();
1715  mOwnsGeom = true;
1716 
1717  deleteCoords();
1718  qDeleteAll( mHoles );
1719  mHoles.clear();
1720  extractCoords( mGeos );
1721  return true;
1722  }
1723  catch ( GEOSException &e )
1724  {
1725  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1726  return false;
1727  }
1728 }
1729 
1731 {
1732  if ( mLF->alwaysShow() )
1733  {
1734  //if feature is set to always show, bump the priority up by orders of magnitude
1735  //so that other feature's labels are unlikely to be placed over the label for this feature
1736  //(negative numbers due to how pal::extract calculates inactive cost)
1737  return -0.2;
1738  }
1739 
1740  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1741 }
1742 
1744 {
1745  bool uprightLabel = false;
1746 
1747  switch ( mLF->layer()->upsidedownLabels() )
1748  {
1749  case Layer::Upright:
1750  uprightLabel = true;
1751  break;
1752  case Layer::ShowDefined:
1753  // upright only dynamic labels
1754  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
1755  {
1756  uprightLabel = true;
1757  }
1758  break;
1759  case Layer::ShowAll:
1760  break;
1761  default:
1762  uprightLabel = true;
1763  }
1764  return uprightLabel;
1765 }
1766 
1767 bool FeaturePart::nextCharPosition( double charWidth, double segmentLength, PointSet *path_positions, int &index, double &currentDistanceAlongSegment,
1768  double &characterStartX, double &characterStartY, double &characterEndX, double &characterEndY ) const
1769 {
1770  // Coordinates this character will start at
1771  if ( qgsDoubleNear( segmentLength, 0.0 ) )
1772  {
1773  // Not allowed to place across on 0 length segments or discontinuities
1774  return false;
1775  }
1776 
1777  double segmentStartX = path_positions->x[index - 1];
1778  double segmentStartY = path_positions->y[index - 1];
1779 
1780  double segmentEndX = path_positions->x[index];
1781  double segmentEndY = path_positions->y[index];
1782 
1783  double segmentDx = segmentEndX - segmentStartX;
1784  double segmentDy = segmentEndY - segmentStartY;
1785 
1786  characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
1787  characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
1788 
1789  // Coordinates this character ends at, calculated below
1790  characterEndX = 0;
1791  characterEndY = 0;
1792 
1793  if ( segmentLength - currentDistanceAlongSegment >= charWidth )
1794  {
1795  // if the distance remaining in this segment is enough, we just go further along the segment
1796  currentDistanceAlongSegment += charWidth;
1797  characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
1798  characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
1799  }
1800  else
1801  {
1802  // If there isn't enough distance left on this segment
1803  // then we need to search until we find the line segment that ends further than ci.width away
1804  do
1805  {
1806  segmentStartX = segmentEndX;
1807  segmentStartY = segmentEndY;
1808  index++;
1809  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1810  {
1811  return false;
1812  }
1813  segmentEndX = path_positions->x[index];
1814  segmentEndY = path_positions->y[index];
1815  }
1816  while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth ); // Distance from start_ to new_
1817 
1818  // Calculate the position to place the end of the character on
1819  GeomFunction::findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
1820 
1821  // Need to calculate distance on the new segment
1822  currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
1823  }
1824  return true;
1825 }
QgsPointXY positionOffset() const
Applies only to "offset from point" placement strategy.
Label below point, slightly right of center.
pal::Layer * layer() const
Gets PAL layer of the label feature. Should be only used internally in PAL.
UpsideDownLabels upsidedownLabels() const
Returns how upside down labels are handled within the layer.
Definition: layer.h:216
double length
Definition: pointset.h:60
Label on bottom-left of point.
int upsideDownCharCount() const
Returns the number of upside down characters for this label position.
void invalidateGeos()
Definition: pointset.cpp:179
static bool candidateSortGrow(const LabelPosition *c1, const LabelPosition *c2)
Sorts label candidates in ascending order of cost.
bool hasFixedPosition() const
Returns true if the feature&#39;s label has a fixed position.
Definition: feature.h:255
double max_char_angle_outside
Definition: feature.h:80
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
double getLabelHeight() const
Definition: feature.h:245
double fixedAngle() const
Returns the fixed angle for the feature&#39;s label.
Definition: feature.h:252
double getWidth() const
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.
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...
int createCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for line feature.
Definition: feature.cpp:582
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label&#39;s permissibleZone().
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
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
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
double cost() const
Returns the candidate label position&#39;s geographical cost.
friend class LabelPosition
Definition: pointset.h:71
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
Label on top of point, slightly right of center.
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
void createGeosGeom() const
Definition: pointset.cpp:125
double getLabelWidth() const
Definition: feature.h:244
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:177
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1690
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:232
double priority() const
Returns the feature&#39;s labeling priority.
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1674
void addSizePenalty(int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1617
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:740
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
int createCandidates(QList< LabelPosition * > &lPos, const GEOSPreparedGeometry *mapBoundary, PointSet *mapShape, RTree< LabelPosition *, double, 2, double > *candidates)
Generic method to generate label candidates for the feature.
Definition: feature.cpp:1536
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
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2821
int createCurvedCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:1154
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
Arranges candidates following the curvature of a line feature. Applies to line layers only...
CharacterInfo * char_info
Definition: feature.h:83
pal::LabelInfo * curvedLabelInfo() const
Gets additional infor required for curved label placement. Returns null if not set.
QgsPalLayerSettings::Placement arrangement() const
Returns the layer&#39;s arrangement policy.
Definition: layer.h:102
double width
Definition: pointset.h:59
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
bool isClosed() const
Returns true if pointset is closed.
Definition: pointset.cpp:849
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:167
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int &orientation, double distance, bool &reversed, bool &flip)
Returns the label position for a curved label at a specific offset along a path.
Definition: feature.cpp:978
QgsGeometry permissibleZone() const
Returns the label&#39;s permissible zone geometry.
Label on left of point.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
bool getShowPartial()
Returns whether partial labels should be allowed.
Definition: pal.cpp:562
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py)
Gets a point a set distance along a line geometry.
Definition: pointset.cpp:779
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1730
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:156
PointSet * parent
Definition: pointset.h:178
double getLabelDistance() const
Definition: feature.h:246
double * x
Definition: pointset.h:169
double ymax
Definition: pointset.h:192
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
double xmin
Definition: pointset.h:189
PointSet * holeOf
Definition: pointset.h:177
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
bool nextCharPosition(double charWidth, double segmentLength, PointSet *path_positions, int &index, double &currentDistanceAlongSegment, double &characterStartX, double &characterStartY, double &characterEndX, double &characterEndY) const
Returns true if the next char position is found. The referenced parameters are updated.
Definition: feature.cpp:1767
double ymin
Definition: pointset.h:191
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
double getY(int i=0) const
Returns the down-left y coordinate.
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
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
int createCandidatesOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:224
bool hasFixedRotation() const
Returns true if the feature&#39;s label has a fixed rotation.
Definition: feature.h:249
double label_height
Definition: feature.h:81
double length() const
Returns length of line geometry.
Definition: pointset.cpp:826
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:66
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1743
LabelPosition * getNextPart() const
void deleteCoords()
Definition: pointset.cpp:206
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
double getX(int i=0) const
Returns the down-left x coordinate.
Main class to handle feature.
Definition: feature.h:96
Offset distance applies from rendered symbol bounds.
void setPartId(int id)
double x
Definition: qgspointxy.h:47
const QgsMargins & visualMargin() const
Returns the visual margin for the label feature.
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:242
double * y
Definition: pointset.h:170
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:268
double getAlpha() const
Returns the angle to rotate text (in rad).
QgsPointXY fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
int createCandidatesForPolygon(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1330
Pal * pal
Definition: layer.h:268
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:545
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:817
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
double x[4]
Definition: pointset.h:54
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
~FeaturePart() override
Delete the feature.
Definition: feature.cpp:77
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:392
double getHeight() const
QList< FeaturePart * > mHoles
Definition: feature.h:306
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
double y[4]
Definition: pointset.h:55
double max_char_angle_inside
Definition: feature.h:79
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
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:85
double distLabel() const
Applies to "around point" placement strategy or linestring features.
GEOSGeometry * mGeos
Definition: pointset.h:165
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
QString name() const
Returns the layer&#39;s name.
Definition: layer.h:96
QgsLabelFeature * mLF
Definition: feature.h:305
Label on top of point, slightly left of center.
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:65
Label on right of point.
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...
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
Definition: feature.cpp:49
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:107
double xmax
Definition: pointset.h:190
int char_num
Definition: feature.h:82
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
bool mOwnsGeom
Definition: pointset.h:166
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:208
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
pal::LineArrangementFlags arrangementFlags() const
Returns the feature&#39;s arrangement flags.
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:151
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...