QGIS API Documentation  3.6.0-Noosa (5873452)
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->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 if ( !line->isClosed() ) // line length < label width => centering label position
860  {
861  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
862  lineStepDistance = -1;
863  totalLineLength = labelWidth;
864  }
865  else
866  {
867  // closed line, not long enough for label => no candidates!
868  currentDistanceAlongLine = std::numeric_limits< double >::max();
869  }
870 
871  double candidateLength;
872  double beta;
873  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
874  int i = 0;
875  while ( currentDistanceAlongLine < totalLineLength - labelWidth )
876  {
877  // calculate positions along linestring corresponding to start and end of current label candidate
878  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
879  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
880 
881  if ( currentDistanceAlongLine < 0 )
882  {
883  // label is bigger than line, use whole available line
884  candidateLength = std::sqrt( ( x[nbPoints - 1] - x[0] ) * ( x[nbPoints - 1] - x[0] )
885  + ( y[nbPoints - 1] - y[0] ) * ( y[nbPoints - 1] - y[0] ) );
886  }
887  else
888  {
889  candidateLength = std::sqrt( ( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
890  }
891 
892  cost = candidateLength / labelWidth;
893  if ( cost > 0.98 )
894  cost = 0.0001;
895  else
896  {
897  // jaggy line has a greater cost
898  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
899  }
900 
901  // penalize positions which are further from the line's midpoint
902  double costCenter = std::fabs( totalLineLength / 2 - ( currentDistanceAlongLine + labelWidth / 2 ) ) / totalLineLength; // <0, 0.5>
903  cost += costCenter / 1000; // < 0, 0.0005 >
904  cost += initialCost;
905 
906  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
907  {
908  angle = 0.0;
909  }
910  else
911  angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
912 
913  beta = angle + M_PI_2;
914 
916  {
917  // find out whether the line direction for this candidate is from right to left
918  bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
919  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
920  bool reversed = ( ( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
921  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
922  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
923 
924  if ( aboveLine )
925  {
926  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
927  positions.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
928  }
929  if ( belowLine )
930  {
931  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
932  positions.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
933  }
934  if ( flags & FLAG_ON_LINE )
935  {
936  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
937  positions.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
938  }
939  }
941  {
942  positions.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
943  }
944  else
945  {
946  // an invalid arrangement?
947  }
948 
949  currentDistanceAlongLine += lineStepDistance;
950 
951  i++;
952 
953  if ( lineStepDistance < 0 )
954  break;
955  }
956 
957  //delete line;
958 
959  delete[] segmentLengths;
960  delete[] distanceToSegment;
961 
962  lPos.append( positions );
963  return lPos.size();
964 }
965 
966 
967 LabelPosition *FeaturePart::curvedPlacementAtOffset( PointSet *path_positions, double *path_distances, int &orientation, int index, double distance, bool &reversed, bool &flip )
968 {
969  // Check that the given distance is on the given index and find the correct index and distance if not
970  while ( distance < 0 && index > 1 )
971  {
972  index--;
973  distance += path_distances[index];
974  }
975 
976  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
977  {
978  return nullptr;
979  }
980 
981  // Same thing, checking if we go off the end
982  while ( index < path_positions->nbPoints && distance > path_distances[index] )
983  {
984  distance -= path_distances[index];
985  index += 1;
986  }
987  if ( index >= path_positions->nbPoints )
988  {
989  return nullptr;
990  }
991 
992  LabelInfo *li = mLF->curvedLabelInfo();
993 
994  double string_height = li->label_height;
995 
996  double segment_length = path_distances[index];
997  if ( qgsDoubleNear( segment_length, 0.0 ) )
998  {
999  // Not allowed to place across on 0 length segments or discontinuities
1000  return nullptr;
1001  }
1002 
1003  if ( orientation == 0 ) // Must be map orientation
1004  {
1005  // Calculate the orientation based on the angle of the path segment under consideration
1006 
1007  double _distance = distance;
1008  int endindex = index;
1009 
1010  for ( int i = 0; i < li->char_num; i++ )
1011  {
1012  LabelInfo::CharacterInfo &ci = li->char_info[i];
1013  double start_x, start_y, end_x, end_y;
1014  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, endindex, _distance, start_x, start_y, end_x, end_y ) )
1015  {
1016  return nullptr;
1017  }
1018  }
1019 
1020  // Determine the angle of the path segment under consideration
1021  double dx = path_positions->x[endindex] - path_positions->x[index];
1022  double dy = path_positions->y[endindex] - path_positions->y[index];
1023  double line_angle = std::atan2( -dy, dx );
1024 
1025  bool isRightToLeft = ( line_angle > 0.55 * M_PI || line_angle < -0.45 * M_PI );
1026  reversed = isRightToLeft;
1027  orientation = isRightToLeft ? -1 : 1;
1028  }
1029 
1030  if ( !showUprightLabels() )
1031  {
1032  if ( orientation < 0 )
1033  {
1034  flip = true; // Report to the caller, that the orientation is flipped
1035  reversed = !reversed;
1036  orientation = 1;
1037  }
1038  }
1039 
1040  LabelPosition *slp = nullptr;
1041  LabelPosition *slp_tmp = nullptr;
1042 
1043  double old_x = path_positions->x[index - 1];
1044  double old_y = path_positions->y[index - 1];
1045 
1046  double new_x = path_positions->x[index];
1047  double new_y = path_positions->y[index];
1048 
1049  double dx = new_x - old_x;
1050  double dy = new_y - old_y;
1051 
1052  double angle = std::atan2( -dy, dx );
1053 
1054  for ( int i = 0; i < li->char_num; i++ )
1055  {
1056  double last_character_angle = angle;
1057 
1058  // grab the next character according to the orientation
1059  LabelInfo::CharacterInfo &ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num - i - 1] );
1060  if ( qgsDoubleNear( ci.width, 0.0 ) )
1061  // Certain scripts rely on zero-width character, skip those to prevent failure (see #15801)
1062  continue;
1063 
1064  double start_x, start_y, end_x, end_y;
1065  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) )
1066  {
1067  delete slp;
1068  return nullptr;
1069  }
1070 
1071  // Calculate angle from the start of the character to the end based on start_/end_ position
1072  angle = std::atan2( start_y - end_y, end_x - start_x );
1073 
1074  // Test last_character_angle vs angle
1075  // since our rendering angle has changed then check against our
1076  // max allowable angle change.
1077  double angle_delta = last_character_angle - angle;
1078  // normalise between -180 and 180
1079  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1080  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1081  if ( ( li->max_char_angle_inside > 0 && angle_delta > 0
1082  && angle_delta > li->max_char_angle_inside * ( M_PI / 180 ) )
1083  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1084  && angle_delta < li->max_char_angle_outside * ( M_PI / 180 ) ) )
1085  {
1086  delete slp;
1087  return nullptr;
1088  }
1089 
1090  // Shift the character downwards since the draw position is specified at the baseline
1091  // and we're calculating the mean line here
1092  double dist = 0.9 * li->label_height / 2;
1093  if ( orientation < 0 )
1094  {
1095  dist = -dist;
1096  flip = true;
1097  }
1098  start_x += dist * std::cos( angle + M_PI_2 );
1099  start_y -= dist * std::sin( angle + M_PI_2 );
1100 
1101  double render_angle = angle;
1102 
1103  double render_x = start_x;
1104  double render_y = start_y;
1105 
1106  // Center the text on the line
1107  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1108  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1109 
1110  if ( orientation < 0 )
1111  {
1112  // rotate in place
1113  render_x += ci.width * std::cos( render_angle ); //- (string_height-2)*sin(render_angle);
1114  render_y -= ci.width * std::sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1115  render_angle += M_PI;
1116  }
1117 
1118  LabelPosition *tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1119  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1120  if ( !slp )
1121  slp = tmp;
1122  else
1123  slp_tmp->setNextPart( tmp );
1124  slp_tmp = tmp;
1125 
1126  // Normalise to 0 <= angle < 2PI
1127  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1128  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1129 
1130  if ( render_angle > M_PI_2 && render_angle < 1.5 * M_PI )
1132  }
1133  // END FOR
1134 
1135  return slp;
1136 }
1137 
1138 static LabelPosition *_createCurvedCandidate( LabelPosition *lp, double angle, double dist )
1139 {
1140  LabelPosition *newLp = new LabelPosition( *lp );
1141  newLp->offsetPosition( dist * std::cos( angle + M_PI_2 ), dist * std::sin( angle + M_PI_2 ) );
1142  return newLp;
1143 }
1144 
1145 int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition * > &lPos, PointSet *mapShape )
1146 {
1147  LabelInfo *li = mLF->curvedLabelInfo();
1148 
1149  // label info must be present
1150  if ( !li || li->char_num == 0 )
1151  return 0;
1152 
1153  // distance calculation
1154  double *path_distances = new double[mapShape->nbPoints];
1155  double total_distance = 0;
1156  double old_x = -1.0, old_y = -1.0;
1157  for ( int i = 0; i < mapShape->nbPoints; i++ )
1158  {
1159  if ( i == 0 )
1160  path_distances[i] = 0;
1161  else
1162  path_distances[i] = std::sqrt( std::pow( old_x - mapShape->x[i], 2 ) + std::pow( old_y - mapShape->y[i], 2 ) );
1163  old_x = mapShape->x[i];
1164  old_y = mapShape->y[i];
1165 
1166  total_distance += path_distances[i];
1167  }
1168 
1169  if ( qgsDoubleNear( total_distance, 0.0 ) )
1170  {
1171  delete[] path_distances;
1172  return 0;
1173  }
1174 
1175  QLinkedList<LabelPosition *> positions;
1176  double delta = std::max( li->label_height, total_distance / mLF->layer()->pal->line_p );
1177 
1178  unsigned long flags = mLF->layer()->arrangementFlags();
1179  if ( flags == 0 )
1180  flags = FLAG_ON_LINE; // default flag
1181 
1182  // generate curved labels
1183  for ( double i = 0; i < total_distance; i += delta )
1184  {
1185  bool flip = false;
1186  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1187  bool reversed = false;
1188 
1189  // an orientation of 0 means try both orientations and choose the best
1190  int orientation = 0;
1191  if ( !( flags & FLAG_MAP_ORIENTATION ) )
1192  {
1193  //... but if we are using line orientation flags, then we can only accept a single orientation,
1194  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1195  orientation = 1;
1196  }
1197 
1198  LabelPosition *slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1199  if ( !slp )
1200  continue;
1201 
1202  // If we placed too many characters upside down
1203  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1204  {
1205  // if labels should be shown upright then retry with the opposite orientation
1206  if ( ( showUprightLabels() && !flip ) )
1207  {
1208  delete slp;
1209  orientation = -orientation;
1210  slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1211  }
1212  }
1213  if ( !slp )
1214  continue;
1215 
1216  // evaluate cost
1217  double angle_diff = 0.0, angle_last = 0.0, diff;
1218  LabelPosition *tmp = slp;
1219  double sin_avg = 0, cos_avg = 0;
1220  while ( tmp )
1221  {
1222  if ( tmp != slp ) // not first?
1223  {
1224  diff = std::fabs( tmp->getAlpha() - angle_last );
1225  if ( diff > 2 * M_PI ) diff -= 2 * M_PI;
1226  diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1227  angle_diff += diff;
1228  }
1229 
1230  sin_avg += std::sin( tmp->getAlpha() );
1231  cos_avg += std::cos( tmp->getAlpha() );
1232  angle_last = tmp->getAlpha();
1233  tmp = tmp->getNextPart();
1234  }
1235 
1236  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1237  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1238  if ( cost < 0.0001 ) cost = 0.0001;
1239 
1240  // penalize positions which are further from the line's midpoint
1241  double labelCenter = i + getLabelWidth() / 2;
1242  double costCenter = std::fabs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1243  cost += costCenter / 1000; // < 0, 0.0005 >
1244  slp->setCost( cost );
1245 
1246  // average angle is calculated with respect to periodicity of angles
1247  double angle_avg = std::atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1248  bool localreversed = flip ? !reversed : reversed;
1249  // displacement - we loop through 3 times, generating above, online then below line placements successively
1250  for ( int i = 0; i <= 2; ++i )
1251  {
1252  LabelPosition *p = nullptr;
1253  if ( i == 0 && ( ( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1254  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1255  if ( i == 1 && flags & FLAG_ON_LINE )
1256  {
1257  p = _createCurvedCandidate( slp, angle_avg, 0 );
1258  p->setCost( p->cost() + 0.002 );
1259  }
1260  if ( i == 2 && ( ( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1261  {
1262  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1263  p->setCost( p->cost() + 0.001 );
1264  }
1265 
1266  if ( p && mLF->permissibleZonePrepared() )
1267  {
1268  bool within = true;
1269  LabelPosition *currentPos = p;
1270  while ( within && currentPos )
1271  {
1272  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1273  currentPos = currentPos->getNextPart();
1274  }
1275  if ( !within )
1276  {
1277  delete p;
1278  p = nullptr;
1279  }
1280  }
1281 
1282  if ( p )
1283  positions.append( p );
1284  }
1285 
1286  // delete original candidate
1287  delete slp;
1288  }
1289 
1290  int nbp = positions.size();
1291  for ( int i = 0; i < nbp; i++ )
1292  {
1293  lPos << positions.takeFirst();
1294  }
1295 
1296  delete[] path_distances;
1297 
1298  return nbp;
1299 }
1300 
1301 /*
1302  * seg 2
1303  * pt3 ____________pt2
1304  * ¦ ¦
1305  * ¦ ¦
1306  * seg 3 ¦ BBOX ¦ seg 1
1307  * ¦ ¦
1308  * ¦____________¦
1309  * pt0 seg 0 pt1
1310  *
1311  */
1312 
1313 int FeaturePart::createCandidatesForPolygon( QList< LabelPosition *> &lPos, PointSet *mapShape )
1314 {
1315  int i;
1316  int j;
1317 
1318  double labelWidth = getLabelWidth();
1319  double labelHeight = getLabelHeight();
1320 
1321  QLinkedList<PointSet *> shapes_toProcess;
1322  QLinkedList<PointSet *> shapes_final;
1323 
1324  mapShape->parent = nullptr;
1325 
1326  shapes_toProcess.append( mapShape );
1327 
1328  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1329 
1330  int nbp;
1331 
1332  if ( !shapes_final.isEmpty() )
1333  {
1334  QLinkedList<LabelPosition *> positions;
1335 
1336  int id = 0; // ids for candidates
1337  double dlx, dly; // delta from label center and bottom-left corner
1338  double alpha = 0.0; // rotation for the label
1339  double px, py;
1340  double dx;
1341  double dy;
1342  int bbid;
1343  double beta;
1344  double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1345  double rx, ry;
1346  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1347  j = 0;
1348 
1349  // Compute bounding box foreach finalShape
1350  while ( !shapes_final.isEmpty() )
1351  {
1352  PointSet *shape = shapes_final.takeFirst();
1353  boxes[j] = shape->compute_chull_bbox();
1354 
1355  if ( shape->parent )
1356  delete shape;
1357 
1358  j++;
1359  }
1360 
1361  //dx = dy = min( yrm, xrm ) / 2;
1362  dx = labelWidth / 2.0;
1363  dy = labelHeight / 2.0;
1364 
1365  int numTry = 0;
1366 
1367  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1368  //then use a smaller limit for number of iterations
1369  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1370 
1371  do
1372  {
1373  for ( bbid = 0; bbid < j; bbid++ )
1374  {
1375  CHullBox *box = boxes[bbid];
1376 
1377  if ( ( box->length * box->width ) > ( xmax - xmin ) * ( ymax - ymin ) * 5 )
1378  {
1379  // Very Large BBOX (should never occur)
1380  continue;
1381  }
1382 
1384  {
1385  //check width/height of bbox is sufficient for label
1386  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1387  mLF->permissibleZone().boundingBox().height() < labelHeight )
1388  {
1389  //no way label can fit in this box, skip it
1390  continue;
1391  }
1392  }
1393 
1394  bool enoughPlace = false;
1396  {
1397  enoughPlace = true;
1398  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1399  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1400  int i, j;
1401 
1402  // Virtual label: center on bbox center, label size = 2x original size
1403  // alpha = 0.
1404  // If all corner are in bbox then place candidates horizontaly
1405  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1406  {
1407  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1408  {
1409  if ( !mapShape->containsPoint( rx, ry ) )
1410  {
1411  enoughPlace = false;
1412  break;
1413  }
1414  }
1415  if ( !enoughPlace )
1416  {
1417  break;
1418  }
1419  }
1420 
1421  } // arrangement== FREE ?
1422 
1423  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1424  {
1425  alpha = 0.0; // HORIZ
1426  }
1427  else if ( box->length > 1.5 * labelWidth && box->width > 1.5 * labelWidth )
1428  {
1429  if ( box->alpha <= M_PI_4 )
1430  {
1431  alpha = box->alpha;
1432  }
1433  else
1434  {
1435  alpha = box->alpha - M_PI_2;
1436  }
1437  }
1438  else if ( box->length > box->width )
1439  {
1440  alpha = box->alpha - M_PI_2;
1441  }
1442  else
1443  {
1444  alpha = box->alpha;
1445  }
1446 
1447  beta = std::atan2( labelHeight, labelWidth ) + alpha;
1448 
1449 
1450  //alpha = box->alpha;
1451 
1452  // delta from label center and down-left corner
1453  dlx = std::cos( beta ) * diago;
1454  dly = std::sin( beta ) * diago;
1455 
1456  double px0, py0;
1457 
1458  px0 = box->width / 2.0;
1459  py0 = box->length / 2.0;
1460 
1461  px0 -= std::ceil( px0 / dx ) * dx;
1462  py0 -= std::ceil( py0 / dy ) * dy;
1463 
1464  for ( px = px0; px <= box->width; px += dx )
1465  {
1466  for ( py = py0; py <= box->length; py += dy )
1467  {
1468 
1469  rx = std::cos( box->alpha ) * px + std::cos( box->alpha - M_PI_2 ) * py;
1470  ry = std::sin( box->alpha ) * px + std::sin( box->alpha - M_PI_2 ) * py;
1471 
1472  rx += box->x[0];
1473  ry += box->y[0];
1474 
1475  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1476  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1477  : mapShape->containsPoint( rx, ry ) );
1478  if ( candidateAcceptable )
1479  {
1480  // cost is set to minimal value, evaluated later
1481  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1482  }
1483  }
1484  }
1485  } // forall box
1486 
1487  nbp = positions.size();
1488  if ( nbp == 0 )
1489  {
1490  dx /= 2;
1491  dy /= 2;
1492  numTry++;
1493  }
1494  }
1495  while ( nbp == 0 && numTry < maxTry );
1496 
1497  nbp = positions.size();
1498 
1499  for ( i = 0; i < nbp; i++ )
1500  {
1501  lPos << positions.takeFirst();
1502  }
1503 
1504  for ( bbid = 0; bbid < j; bbid++ )
1505  {
1506  delete boxes[bbid];
1507  }
1508 
1509  delete[] boxes;
1510  }
1511  else
1512  {
1513  nbp = 0;
1514  }
1515 
1516  return nbp;
1517 }
1518 
1519 int FeaturePart::createCandidates( QList< LabelPosition *> &lPos,
1520  const GEOSPreparedGeometry *mapBoundary,
1521  PointSet *mapShape, RTree<LabelPosition *, double, 2, double> *candidates )
1522 {
1523  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1524 
1525  if ( mLF->hasFixedPosition() )
1526  {
1527  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1528  }
1529  else
1530  {
1531  switch ( type )
1532  {
1533  case GEOS_POINT:
1535  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1537  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1538  else
1539  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1540  break;
1541  case GEOS_LINESTRING:
1542  if ( mLF->layer()->isCurved() )
1543  createCurvedCandidatesAlongLine( lPos, mapShape );
1544  else
1545  createCandidatesAlongLine( lPos, mapShape );
1546  break;
1547 
1548  case GEOS_POLYGON:
1549  switch ( mLF->layer()->arrangement() )
1550  {
1553  double cx, cy;
1554  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1556  createCandidatesOverPoint( cx, cy, lPos, angle );
1557  else
1558  createCandidatesAroundPoint( cx, cy, lPos, angle );
1559  break;
1561  createCandidatesAlongLine( lPos, mapShape );
1562  break;
1564  createCurvedCandidatesAlongLine( lPos, mapShape );
1565  break;
1566  default:
1567  createCandidatesForPolygon( lPos, mapShape );
1568  break;
1569  }
1570  }
1571  }
1572 
1573  // purge candidates that are outside the bbox
1574 
1575  QMutableListIterator< LabelPosition *> i( lPos );
1576  while ( i.hasNext() )
1577  {
1578  LabelPosition *pos = i.next();
1579  bool outside = false;
1580 
1581  if ( mLF->layer()->pal->getShowPartial() )
1582  outside = !pos->intersects( mapBoundary );
1583  else
1584  outside = !pos->within( mapBoundary );
1585  if ( outside )
1586  {
1587  i.remove();
1588  delete pos;
1589  }
1590  else // this one is OK
1591  {
1592  pos->insertIntoIndex( candidates );
1593  }
1594  }
1595 
1596  std::sort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1597  return lPos.count();
1598 }
1599 
1600 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4] )
1601 {
1602  if ( !mGeos )
1603  createGeosGeom();
1604 
1605  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
1606  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1607 
1608  double sizeCost = 0;
1609  if ( geomType == GEOS_LINESTRING )
1610  {
1611  double length;
1612  try
1613  {
1614  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1615  return; // failed to calculate length
1616  }
1617  catch ( GEOSException &e )
1618  {
1619  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1620  return;
1621  }
1622  double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
1623  if ( length >= bbox_length / 4 )
1624  return; // the line is longer than quarter of height or width - don't penalize it
1625 
1626  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1627  }
1628  else if ( geomType == GEOS_POLYGON )
1629  {
1630  double area;
1631  try
1632  {
1633  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1634  return;
1635  }
1636  catch ( GEOSException &e )
1637  {
1638  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1639  return;
1640  }
1641  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1642  if ( area >= bbox_area / 16 )
1643  return; // covers more than 1/16 of our view - don't penalize it
1644 
1645  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1646  }
1647  else
1648  return; // no size penalty for points
1649 
1650  // apply the penalty
1651  for ( int i = 0; i < nbp; i++ )
1652  {
1653  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1654  }
1655 }
1656 
1658 {
1659  if ( !p2->mGeos )
1660  p2->createGeosGeom();
1661 
1662  try
1663  {
1664  return ( GEOSPreparedTouches_r( QgsGeos::getGEOSHandler(), preparedGeom(), p2->mGeos ) == 1 );
1665  }
1666  catch ( GEOSException &e )
1667  {
1668  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1669  return false;
1670  }
1671 }
1672 
1674 {
1675  if ( !mGeos )
1676  createGeosGeom();
1677  if ( !other->mGeos )
1678  other->createGeosGeom();
1679 
1680  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
1681  try
1682  {
1683  GEOSGeometry *g1 = GEOSGeom_clone_r( ctxt, mGeos );
1684  GEOSGeometry *g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1685  GEOSGeometry *geoms[2] = { g1, g2 };
1686  geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
1687  geos::unique_ptr gTmp( GEOSLineMerge_r( ctxt, g.get() ) );
1688 
1689  if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
1690  {
1691  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1692  return false;
1693  }
1694  invalidateGeos();
1695 
1696  // set up new geometry
1697  mGeos = gTmp.release();
1698  mOwnsGeom = true;
1699 
1700  deleteCoords();
1701  qDeleteAll( mHoles );
1702  mHoles.clear();
1703  extractCoords( mGeos );
1704  return true;
1705  }
1706  catch ( GEOSException &e )
1707  {
1708  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1709  return false;
1710  }
1711 }
1712 
1714 {
1715  if ( mLF->alwaysShow() )
1716  {
1717  //if feature is set to always show, bump the priority up by orders of magnitude
1718  //so that other feature's labels are unlikely to be placed over the label for this feature
1719  //(negative numbers due to how pal::extract calculates inactive cost)
1720  return -0.2;
1721  }
1722 
1723  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1724 }
1725 
1727 {
1728  bool uprightLabel = false;
1729 
1730  switch ( mLF->layer()->upsidedownLabels() )
1731  {
1732  case Layer::Upright:
1733  uprightLabel = true;
1734  break;
1735  case Layer::ShowDefined:
1736  // upright only dynamic labels
1737  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
1738  {
1739  uprightLabel = true;
1740  }
1741  break;
1742  case Layer::ShowAll:
1743  break;
1744  default:
1745  uprightLabel = true;
1746  }
1747  return uprightLabel;
1748 }
1749 
1750 bool FeaturePart::nextCharPosition( double charWidth, double segment_length, PointSet *path_positions, int &index, double &distance,
1751  double &start_x, double &start_y, double &end_x, double &end_y ) const
1752 {
1753  // Coordinates this character will start at
1754  if ( qgsDoubleNear( segment_length, 0.0 ) )
1755  {
1756  // Not allowed to place across on 0 length segments or discontinuities
1757  return false;
1758  }
1759 
1760  double old_x = path_positions->x[index - 1];
1761  double old_y = path_positions->y[index - 1];
1762 
1763  double new_x = path_positions->x[index];
1764  double new_y = path_positions->y[index];
1765 
1766  double dx = new_x - old_x;
1767  double dy = new_y - old_y;
1768 
1769  start_x = old_x + dx * distance / segment_length;
1770  start_y = old_y + dy * distance / segment_length;
1771 
1772  // Coordinates this character ends at, calculated below
1773  end_x = 0;
1774  end_y = 0;
1775 
1776  if ( segment_length - distance >= charWidth )
1777  {
1778  // if the distance remaining in this segment is enough, we just go further along the segment
1779  distance += charWidth;
1780  end_x = old_x + dx * distance / segment_length;
1781  end_y = old_y + dy * distance / segment_length;
1782  }
1783  else
1784  {
1785  // If there isn't enough distance left on this segment
1786  // then we need to search until we find the line segment that ends further than ci.width away
1787  do
1788  {
1789  old_x = new_x;
1790  old_y = new_y;
1791  index++;
1792  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1793  {
1794  return false;
1795  }
1796  new_x = path_positions->x[index];
1797  new_y = path_positions->y[index];
1798  dx = new_x - old_x;
1799  dy = new_y - old_y;
1800  }
1801  while ( std::sqrt( std::pow( start_x - new_x, 2 ) + std::pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_
1802 
1803  // Calculate the position to place the end of the character on
1804  GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );
1805 
1806  // Need to calculate distance on the new segment
1807  distance = std::sqrt( std::pow( old_x - end_x, 2 ) + std::pow( old_y - end_y, 2 ) );
1808  }
1809  return true;
1810 }
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.
int createCandidatesForPolygon(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1313
void invalidateGeos()
Definition: pointset.cpp:179
double distLabel() const
Applies to "around point" placement strategy or linestring features.
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:242
double fixedAngle() const
Returns the fixed angle for the feature&#39;s label.
Definition: feature.h:253
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:250
double getY(int i=0) const
Returns 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...
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
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().
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.
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
bool isClosed() const
Returns true if pointset is closed.
Definition: pointset.cpp:849
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1673
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:1657
const QgsMargins & visualMargin() const
Returns the visual margin for the label feature.
double getLabelDistance() const
Definition: feature.h:247
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
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:2777
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
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:967
double cost() const
Returns the candidate label position&#39;s geographical cost.
pal::LabelInfo * curvedLabelInfo() const
Gets 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:1750
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
void addSizePenalty(int nbp, QList< LabelPosition *> &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1600
PointSet * parent
Definition: pointset.h:178
double * x
Definition: pointset.h:169
double ymax
Definition: pointset.h:192
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
double xmin
Definition: pointset.h:189
double getHeight() const
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
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).
double ymin
Definition: pointset.h:191
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:1713
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)
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:1519
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:66
pal::Layer * layer() const
Gets 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
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
double getLabelHeight() const
Definition: feature.h:246
void deleteCoords()
Definition: pointset.cpp:206
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:256
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:170
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)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:545
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
Returns the angle to rotate text (in rad).
double length() const
Returns length of line geometry.
Definition: pointset.cpp:826
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
~FeaturePart() override
Delete the feature.
Definition: feature.cpp:77
QList< FeaturePart * > mHoles
Definition: feature.h:307
double getWidth() const
double getX(int i=0) const
Returns 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:245
GEOSGeometry * mGeos
Definition: pointset.h:165
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
QgsLabelFeature * mLF
Definition: feature.h:306
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.
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
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
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.
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:740
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 xmax
Definition: pointset.h:190
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:1726
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:209
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:1145
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...