QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 "qgslabeling.h"
44 #include "qgspolygon.h"
45 #include <QLinkedList>
46 #include <cmath>
47 #include <cfloat>
48 
49 using namespace pal;
50 
51 FeaturePart::FeaturePart( QgsLabelFeature *feat, const GEOSGeometry *geom )
52  : mLF( feat )
53 {
54  // we'll remove const, but we won't modify that geometry
55  mGeos = const_cast<GEOSGeometry *>( geom );
56  mOwnsGeom = false; // geometry is owned by Feature class
57 
58  extractCoords( geom );
59 
60  holeOf = nullptr;
61  for ( int i = 0; i < mHoles.count(); i++ )
62  {
63  mHoles.at( i )->holeOf = this;
64  }
65 
66 }
67 
69  : PointSet( other )
70  , mLF( other.mLF )
71 {
72  for ( const FeaturePart *hole : qgis::as_const( other.mHoles ) )
73  {
74  mHoles << new FeaturePart( *hole );
75  mHoles.last()->holeOf = this;
76  }
77 }
78 
80 {
81  // X and Y are deleted in PointSet
82 
83  qDeleteAll( mHoles );
84  mHoles.clear();
85 }
86 
87 void FeaturePart::extractCoords( const GEOSGeometry *geom )
88 {
89  const GEOSCoordSequence *coordSeq = nullptr;
90  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
91 
92  type = GEOSGeomTypeId_r( geosctxt, geom );
93 
94  if ( type == GEOS_POLYGON )
95  {
96  if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
97  {
98  int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
99 
100  for ( int i = 0; i < numHoles; ++i )
101  {
102  const GEOSGeometry *interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
103  FeaturePart *hole = new FeaturePart( mLF, interior );
104  hole->holeOf = nullptr;
105  // possibly not needed. it's not done for the exterior ring, so I'm not sure
106  // why it's just done here...
107  GeomFunction::reorderPolygon( hole->nbPoints, hole->x, hole->y );
108 
109  mHoles << hole;
110  }
111  }
112 
113  // use exterior ring for the extraction of coordinates that follows
114  geom = GEOSGetExteriorRing_r( geosctxt, geom );
115  }
116  else
117  {
118  qDeleteAll( mHoles );
119  mHoles.clear();
120  }
121 
122  // find out number of points
123  nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
124  coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
125 
126  // initialize bounding box
127  xmin = ymin = std::numeric_limits<double>::max();
128  xmax = ymax = std::numeric_limits<double>::lowest();
129 
130  // initialize coordinate arrays
131  deleteCoords();
132  x.resize( nbPoints );
133  y.resize( nbPoints );
134 
135  for ( int i = 0; i < nbPoints; ++i )
136  {
137 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
138  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, i, &x[i], &y[i] );
139 #else
140  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] );
141  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] );
142 #endif
143 
144  xmax = x[i] > xmax ? x[i] : xmax;
145  xmin = x[i] < xmin ? x[i] : xmin;
146 
147  ymax = y[i] > ymax ? y[i] : ymax;
148  ymin = y[i] < ymin ? y[i] : ymin;
149  }
150 }
151 
153 {
154  return mLF->layer();
155 }
156 
158 {
159  return mLF->id();
160 }
161 
163 {
165 }
166 
168 {
169  if ( mCachedMaxLineCandidates > 0 )
170  return mCachedMaxLineCandidates;
171 
172  const double l = length();
173  if ( l > 0 )
174  {
175  const std::size_t candidatesForLineLength = static_cast< std::size_t >( std::ceil( mLF->layer()->mPal->maximumLineCandidatesPerMapUnit() * l ) );
176  const std::size_t maxForLayer = mLF->layer()->maximumLineLabelCandidates();
177  if ( maxForLayer == 0 )
178  mCachedMaxLineCandidates = candidatesForLineLength;
179  else
180  mCachedMaxLineCandidates = std::min( candidatesForLineLength, maxForLayer );
181  }
182  else
183  {
184  mCachedMaxLineCandidates = 1;
185  }
186  return mCachedMaxLineCandidates;
187 }
188 
190 {
191  if ( mCachedMaxPolygonCandidates > 0 )
192  return mCachedMaxPolygonCandidates;
193 
194  const double a = area();
195  if ( a > 0 )
196  {
197  const std::size_t candidatesForArea = static_cast< std::size_t >( std::ceil( mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * a ) );
198  const std::size_t maxForLayer = mLF->layer()->maximumPolygonLabelCandidates();
199  if ( maxForLayer == 0 )
200  mCachedMaxPolygonCandidates = candidatesForArea;
201  else
202  mCachedMaxPolygonCandidates = std::min( candidatesForArea, maxForLayer );
203  }
204  else
205  {
206  mCachedMaxPolygonCandidates = 1;
207  }
208  return mCachedMaxPolygonCandidates;
209 }
210 
212 {
213  if ( !part )
214  return false;
215 
216  if ( mLF->layer()->name() != part->layer()->name() )
217  return false;
218 
219  if ( mLF->id() == part->featureId() )
220  return true;
221 
222  // any part of joined features are also treated as having the same label feature
223  int connectedFeatureId = mLF->layer()->connectedFeatureId( mLF->id() );
224  return connectedFeatureId >= 0 && connectedFeatureId == mLF->layer()->connectedFeatureId( part->featureId() );
225 }
226 
227 LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
228 {
229  QPointF quadOffset = mLF->quadOffset();
230  qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
231 
232  if ( quadOffsetX < 0 )
233  {
234  if ( quadOffsetY < 0 )
235  {
237  }
238  else if ( quadOffsetY > 0 )
239  {
241  }
242  else
243  {
245  }
246  }
247  else if ( quadOffsetX > 0 )
248  {
249  if ( quadOffsetY < 0 )
250  {
252  }
253  else if ( quadOffsetY > 0 )
254  {
256  }
257  else
258  {
260  }
261  }
262  else
263  {
264  if ( quadOffsetY < 0 )
265  {
267  }
268  else if ( quadOffsetY > 0 )
269  {
271  }
272  else
273  {
275  }
276  }
277 }
278 
280 {
281  return mTotalRepeats;
282 }
283 
284 void FeaturePart::setTotalRepeats( int totalRepeats )
285 {
286  mTotalRepeats = totalRepeats;
287 }
288 
289 std::size_t FeaturePart::createCandidateCenteredOverPoint( double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle )
290 {
291  // get from feature
292  double labelW = getLabelWidth( angle );
293  double labelH = getLabelHeight( angle );
294 
295  double cost = 0.00005;
296  int id = lPos.size();
297 
298  double xdiff = -labelW / 2.0;
299  double ydiff = -labelH / 2.0;
300 
302 
303  double lx = x + xdiff;
304  double ly = y + ydiff;
305 
306  if ( mLF->permissibleZonePrepared() )
307  {
308  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
309  {
310  return 0;
311  }
312  }
313 
314  lPos.emplace_back( qgis::make_unique< LabelPosition >( id, lx, ly, labelW, labelH, angle, cost, this, false, LabelPosition::QuadrantOver ) );
315  return 1;
316 }
317 
318 std::size_t FeaturePart::createCandidatesOverPoint( double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle )
319 {
320  // get from feature
321  double labelW = getLabelWidth( angle );
322  double labelH = getLabelHeight( angle );
323 
324  double cost = 0.0001;
325  int id = lPos.size();
326 
327  double xdiff = -labelW / 2.0;
328  double ydiff = -labelH / 2.0;
329 
331 
332  if ( !qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
333  {
334  xdiff += labelW / 2.0 * mLF->quadOffset().x();
335  }
336  if ( !qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
337  {
338  ydiff += labelH / 2.0 * mLF->quadOffset().y();
339  }
340 
341  if ( ! mLF->hasFixedPosition() )
342  {
343  if ( !qgsDoubleNear( angle, 0.0 ) )
344  {
345  double xd = xdiff * std::cos( angle ) - ydiff * std::sin( angle );
346  double yd = xdiff * std::sin( angle ) + ydiff * std::cos( angle );
347  xdiff = xd;
348  ydiff = yd;
349  }
350  }
351 
353  {
354  //if in "around point" placement mode, then we use the label distance to determine
355  //the label's offset
356  if ( qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
357  {
358  ydiff += mLF->quadOffset().y() * mLF->distLabel();
359  }
360  else if ( qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
361  {
362  xdiff += mLF->quadOffset().x() * mLF->distLabel();
363  }
364  else
365  {
366  xdiff += mLF->quadOffset().x() * M_SQRT1_2 * mLF->distLabel();
367  ydiff += mLF->quadOffset().y() * M_SQRT1_2 * mLF->distLabel();
368  }
369  }
370  else
371  {
372  if ( !qgsDoubleNear( mLF->positionOffset().x(), 0.0 ) )
373  {
374  xdiff += mLF->positionOffset().x();
375  }
376  if ( !qgsDoubleNear( mLF->positionOffset().y(), 0.0 ) )
377  {
378  ydiff += mLF->positionOffset().y();
379  }
380  }
381 
382  double lx = x + xdiff;
383  double ly = y + ydiff;
384 
385  if ( mLF->permissibleZonePrepared() )
386  {
387  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
388  {
389  return 0;
390  }
391  }
392 
393  lPos.emplace_back( qgis::make_unique< LabelPosition >( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() ) );
394  return 1;
395 }
396 
397 std::unique_ptr<LabelPosition> FeaturePart::createCandidatePointOnSurface( PointSet *mapShape )
398 {
399  double px, py;
400  try
401  {
402  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
403  geos::unique_ptr pointGeom( GEOSPointOnSurface_r( geosctxt, mapShape->geos() ) );
404  if ( pointGeom )
405  {
406  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
407 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
408  unsigned int nPoints = 0;
409  GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
410  if ( nPoints == 0 )
411  return nullptr;
412  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
413 #else
414  GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
415  GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
416 #endif
417  }
418  }
419  catch ( GEOSException &e )
420  {
421  qWarning( "GEOS exception: %s", e.what() );
422  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
423  return nullptr;
424  }
425 
426  return qgis::make_unique< LabelPosition >( 0, px, py, getLabelWidth(), getLabelHeight(), 0.0, 0.0, this, false, LabelPosition::QuadrantOver );
427 }
428 
429 void createCandidateAtOrderedPositionOverPoint( double &labelX, double &labelY, LabelPosition::Quadrant &quadrant, double x, double y, double labelWidth, double labelHeight, QgsPalLayerSettings::PredefinedPointPosition position, double distanceToLabel, const QgsMargins &visualMargin, double symbolWidthOffset, double symbolHeightOffset )
430 {
431  double alpha = 0.0;
432  double deltaX = 0;
433  double deltaY = 0;
434  switch ( position )
435  {
438  alpha = 3 * M_PI_4;
439  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
440  deltaY = -visualMargin.bottom() + symbolHeightOffset;
441  break;
442 
444  quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
445  alpha = M_PI_2;
446  deltaX = -labelWidth / 4.0 - visualMargin.left();
447  deltaY = -visualMargin.bottom() + symbolHeightOffset;
448  break;
449 
451  quadrant = LabelPosition::QuadrantAbove;
452  alpha = M_PI_2;
453  deltaX = -labelWidth / 2.0;
454  deltaY = -visualMargin.bottom() + symbolHeightOffset;
455  break;
456 
458  quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
459  alpha = M_PI_2;
460  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right();
461  deltaY = -visualMargin.bottom() + symbolHeightOffset;
462  break;
463 
466  alpha = M_PI_4;
467  deltaX = - visualMargin.left() + symbolWidthOffset;
468  deltaY = -visualMargin.bottom() + symbolHeightOffset;
469  break;
470 
472  quadrant = LabelPosition::QuadrantLeft;
473  alpha = M_PI;
474  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
475  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
476  break;
477 
479  quadrant = LabelPosition::QuadrantRight;
480  alpha = 0.0;
481  deltaX = -visualMargin.left() + symbolWidthOffset;
482  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
483  break;
484 
487  alpha = 5 * M_PI_4;
488  deltaX = -labelWidth + visualMargin.right() - symbolWidthOffset;
489  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
490  break;
491 
493  quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
494  alpha = 3 * M_PI_2;
495  deltaX = -labelWidth / 4.0 - visualMargin.left();
496  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
497  break;
498 
500  quadrant = LabelPosition::QuadrantBelow;
501  alpha = 3 * M_PI_2;
502  deltaX = -labelWidth / 2.0;
503  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
504  break;
505 
507  quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
508  alpha = 3 * M_PI_2;
509  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right();
510  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
511  break;
512 
515  alpha = 7 * M_PI_4;
516  deltaX = -visualMargin.left() + symbolWidthOffset;
517  deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset;
518  break;
519  }
520 
521  //have bearing, distance - calculate reference point
522  double referenceX = std::cos( alpha ) * distanceToLabel + x;
523  double referenceY = std::sin( alpha ) * distanceToLabel + y;
524 
525  labelX = referenceX + deltaX;
526  labelY = referenceY + deltaY;
527 }
528 
529 std::size_t FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle )
530 {
531  const QVector< QgsPalLayerSettings::PredefinedPointPosition > positions = mLF->predefinedPositionOrder();
532  double labelWidth = getLabelWidth( angle );
533  double labelHeight = getLabelHeight( angle );
534  double distanceToLabel = getLabelDistance();
535  const QgsMargins &visualMargin = mLF->visualMargin();
536 
537  double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
538  double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );
539 
540  double cost = 0.0001;
541  std::size_t i = lPos.size();
542 
543  const std::size_t maxNumberCandidates = mLF->layer()->maximumPointLabelCandidates();
544  std::size_t created = 0;
545  for ( QgsPalLayerSettings::PredefinedPointPosition position : positions )
546  {
548 
549  double labelX = 0;
550  double labelY = 0;
551  createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant, x, y, labelWidth, labelHeight, position, distanceToLabel, visualMargin, symbolWidthOffset, symbolHeightOffset );
552 
553  if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
554  {
555  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant ) );
556  created++;
557  //TODO - tweak
558  cost += 0.001;
559  if ( maxNumberCandidates > 0 && created >= maxNumberCandidates )
560  break;
561  }
562  ++i;
563  }
564 
565  return created;
566 }
567 
568 std::size_t FeaturePart::createCandidatesAroundPoint( double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle )
569 {
570  double labelWidth = getLabelWidth( angle );
571  double labelHeight = getLabelHeight( angle );
572  double distanceToLabel = getLabelDistance();
573 
574  std::size_t maxNumberCandidates = mLF->layer()->maximumPointLabelCandidates();
575  if ( maxNumberCandidates == 0 )
576  maxNumberCandidates = 16;
577 
578  int icost = 0;
579  int inc = 2;
580  int id = lPos.size();
581 
582  double candidateAngleIncrement = 2 * M_PI / maxNumberCandidates; /* angle bw 2 pos */
583 
584  /* various angles */
585  double a90 = M_PI_2;
586  double a180 = M_PI;
587  double a270 = a180 + a90;
588  double a360 = 2 * M_PI;
589 
590  double gamma1, gamma2;
591 
592  if ( distanceToLabel > 0 )
593  {
594  gamma1 = std::atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
595  gamma2 = std::atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
596  }
597  else
598  {
599  gamma1 = gamma2 = a90 / 3.0;
600  }
601 
602  if ( gamma1 > a90 / 3.0 )
603  gamma1 = a90 / 3.0;
604 
605  if ( gamma2 > a90 / 3.0 )
606  gamma2 = a90 / 3.0;
607 
608  std::size_t numberCandidatesGenerated = 0;
609 
610  std::size_t i;
611  double angleToCandidate;
612  for ( i = 0, angleToCandidate = M_PI_4; i < maxNumberCandidates; i++, angleToCandidate += candidateAngleIncrement )
613  {
614  double labelX = x;
615  double labelY = y;
616 
617  if ( angleToCandidate > a360 )
618  angleToCandidate -= a360;
619 
621 
622  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
623  {
624  labelX += distanceToLabel;
625  double iota = ( angleToCandidate + gamma1 );
626  if ( iota > a360 - gamma1 )
627  iota -= a360;
628 
629  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
630  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
631 
632  quadrant = LabelPosition::QuadrantRight;
633  }
634  else if ( angleToCandidate < a90 - gamma2 ) // top-right
635  {
636  labelX += distanceToLabel * std::cos( angleToCandidate );
637  labelY += distanceToLabel * std::sin( angleToCandidate );
639  }
640  else if ( angleToCandidate < a90 + gamma2 ) // top
641  {
642  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
643  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
644  labelY += distanceToLabel;
645  quadrant = LabelPosition::QuadrantAbove;
646  }
647  else if ( angleToCandidate < a180 - gamma1 ) // top left
648  {
649  labelX += distanceToLabel * std::cos( angleToCandidate ) - labelWidth;
650  labelY += distanceToLabel * std::sin( angleToCandidate );
652  }
653  else if ( angleToCandidate < a180 + gamma1 ) // left
654  {
655  labelX += -distanceToLabel - labelWidth;
656  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
657  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
658  quadrant = LabelPosition::QuadrantLeft;
659  }
660  else if ( angleToCandidate < a270 - gamma2 ) // down - left
661  {
662  labelX += distanceToLabel * std::cos( angleToCandidate ) - labelWidth;
663  labelY += distanceToLabel * std::sin( angleToCandidate ) - labelHeight;
665  }
666  else if ( angleToCandidate < a270 + gamma2 ) // down
667  {
668  labelY += -distanceToLabel - labelHeight;
669  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
670  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
671  quadrant = LabelPosition::QuadrantBelow;
672  }
673  else if ( angleToCandidate < a360 ) // down - right
674  {
675  labelX += distanceToLabel * std::cos( angleToCandidate );
676  labelY += distanceToLabel * std::sin( angleToCandidate ) - labelHeight;
678  }
679 
680  double cost;
681 
682  if ( maxNumberCandidates == 1 )
683  cost = 0.0001;
684  else
685  cost = 0.0001 + 0.0020 * double( icost ) / double( maxNumberCandidates - 1 );
686 
687 
688  if ( mLF->permissibleZonePrepared() )
689  {
690  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
691  {
692  continue;
693  }
694  }
695 
696  lPos.emplace_back( qgis::make_unique< LabelPosition >( id + i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant ) );
697  numberCandidatesGenerated++;
698 
699  icost += inc;
700 
701  if ( icost == static_cast< int >( maxNumberCandidates ) )
702  {
703  icost = static_cast< int >( maxNumberCandidates ) - 1;
704  inc = -2;
705  }
706  else if ( icost > static_cast< int >( maxNumberCandidates ) )
707  {
708  icost = static_cast< int >( maxNumberCandidates ) - 2;
709  inc = -2;
710  }
711 
712  }
713 
714  return numberCandidatesGenerated;
715 }
716 
717 std::size_t FeaturePart::createCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal )
718 {
719  if ( allowOverrun )
720  {
721  double shapeLength = mapShape->length();
722  if ( totalRepeats() > 1 && shapeLength < getLabelWidth() )
723  return 0;
724  else if ( shapeLength < getLabelWidth() - 2 * std::min( getLabelWidth(), mLF->overrunDistance() ) )
725  {
726  // label doesn't fit on this line, don't waste time trying to make candidates
727  return 0;
728  }
729  }
730 
731  //prefer to label along straightish segments:
732  std::size_t candidates = 0;
733 
735  candidates = createCandidatesAlongLineNearStraightSegments( lPos, mapShape, pal );
736 
737  const std::size_t candidateTargetCount = maximumLineCandidates();
738  if ( candidates < candidateTargetCount )
739  {
740  // but not enough candidates yet, so fallback to labeling near whole line's midpoint
741  candidates = createCandidatesAlongLineNearMidpoint( lPos, mapShape, candidates > 0 ? 0.01 : 0.0, pal );
742  }
743  return candidates;
744 }
745 
746 std::size_t FeaturePart::createHorizontalCandidatesAlongLine( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, Pal *pal )
747 {
748  const double labelWidth = getLabelWidth();
749  const double labelHeight = getLabelHeight();
750 
751  PointSet *line = mapShape;
752  int nbPoints = line->nbPoints;
753  std::vector< double > &x = line->x;
754  std::vector< double > &y = line->y;
755 
756  std::vector< double > segmentLengths( nbPoints - 1 ); // segments lengths distance bw pt[i] && pt[i+1]
757  std::vector< double >distanceToSegment( nbPoints ); // absolute distance bw pt[0] and pt[i] along the line
758 
759  double totalLineLength = 0.0; // line length
760  for ( int i = 0; i < line->nbPoints - 1; i++ )
761  {
762  if ( i == 0 )
763  distanceToSegment[i] = 0;
764  else
765  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
766 
767  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
768  totalLineLength += segmentLengths[i];
769  }
770  distanceToSegment[line->nbPoints - 1] = totalLineLength;
771 
772  const std::size_t candidateTargetCount = maximumLineCandidates();
773  double lineStepDistance = 0;
774 
775  const double lineAnchorPoint = totalLineLength * mLF->lineAnchorPercent();
776  double currentDistanceAlongLine = lineStepDistance;
777  switch ( mLF->lineAnchorType() )
778  {
780  lineStepDistance = totalLineLength / ( candidateTargetCount + 1 ); // distance to move along line with each candidate
781  break;
782 
784  currentDistanceAlongLine = lineAnchorPoint;
785  lineStepDistance = -1;
786  break;
787  }
788 
789  double candidateCenterX, candidateCenterY;
790  int i = 0;
791  while ( currentDistanceAlongLine <= totalLineLength )
792  {
793  if ( pal->isCanceled() )
794  {
795  return lPos.size();
796  }
797 
798  line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateCenterX, &candidateCenterY );
799 
800  // penalize positions which are further from the line's anchor point
801  double cost = std::fabs( lineAnchorPoint - currentDistanceAlongLine ) / totalLineLength; // <0, 0.5>
802  cost /= 1000; // < 0, 0.0005 >
803 
804  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateCenterX - labelWidth / 2, candidateCenterY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this, false, LabelPosition::QuadrantOver ) );
805 
806  currentDistanceAlongLine += lineStepDistance;
807 
808  i++;
809 
810  if ( lineStepDistance < 0 )
811  break;
812  }
813 
814  return lPos.size();
815 }
816 
817 std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal )
818 {
819  double labelWidth = getLabelWidth();
820  double labelHeight = getLabelHeight();
821  double distanceLineToLabel = getLabelDistance();
822  QgsLabeling::LinePlacementFlags flags = mLF->arrangementFlags();
823  if ( flags == 0 )
824  flags = QgsLabeling::LinePlacementFlag::OnLine; // default flag
825 
826  // 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
827  QVector< int > extremeAngleNodes;
828  PointSet *line = mapShape;
829  int numberNodes = line->nbPoints;
830  std::vector< double > &x = line->x;
831  std::vector< double > &y = line->y;
832 
833  // closed line? if so, we need to handle the final node angle
834  bool closedLine = qgsDoubleNear( x[0], x[ numberNodes - 1] ) && qgsDoubleNear( y[0], y[numberNodes - 1 ] );
835  for ( int i = 1; i <= numberNodes - ( closedLine ? 1 : 2 ); ++i )
836  {
837  double x1 = x[i - 1];
838  double x2 = x[i];
839  double x3 = x[ i == numberNodes - 1 ? 1 : i + 1]; // wraparound for closed linestrings
840  double y1 = y[i - 1];
841  double y2 = y[i];
842  double y3 = y[ i == numberNodes - 1 ? 1 : i + 1]; // wraparound for closed linestrings
843  if ( qgsDoubleNear( y2, y3 ) && qgsDoubleNear( x2, x3 ) )
844  continue;
845  if ( qgsDoubleNear( y1, y2 ) && qgsDoubleNear( x1, x2 ) )
846  continue;
847  double vertexAngle = M_PI - ( std::atan2( y3 - y2, x3 - x2 ) - std::atan2( y2 - y1, x2 - x1 ) );
848  vertexAngle = QgsGeometryUtils::normalizedAngle( vertexAngle );
849 
850  // extreme angles form more than 45 degree angle at a node - these are the ones we don't want labels to cross
851  if ( vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0 )
852  extremeAngleNodes << i;
853  }
854  extremeAngleNodes << numberNodes - 1;
855 
856  if ( extremeAngleNodes.isEmpty() )
857  {
858  // no extreme angles - createCandidatesAlongLineNearMidpoint will be more appropriate
859  return 0;
860  }
861 
862  // calculate lengths of segments, and work out longest straight-ish segment
863  std::vector< double > segmentLengths( numberNodes - 1 ); // segments lengths distance bw pt[i] && pt[i+1]
864  std::vector< double > distanceToSegment( numberNodes ); // absolute distance bw pt[0] and pt[i] along the line
865  double totalLineLength = 0.0;
866  QVector< double > straightSegmentLengths;
867  QVector< double > straightSegmentAngles;
868  straightSegmentLengths.reserve( extremeAngleNodes.size() + 1 );
869  straightSegmentAngles.reserve( extremeAngleNodes.size() + 1 );
870  double currentStraightSegmentLength = 0;
871  double longestSegmentLength = 0;
872  int segmentIndex = 0;
873  double segmentStartX = x[0];
874  double segmentStartY = y[0];
875  for ( int i = 0; i < numberNodes - 1; i++ )
876  {
877  if ( i == 0 )
878  distanceToSegment[i] = 0;
879  else
880  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
881 
882  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
883  totalLineLength += segmentLengths[i];
884  if ( extremeAngleNodes.contains( i ) )
885  {
886  // at an extreme angle node, so reset counters
887  straightSegmentLengths << currentStraightSegmentLength;
888  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( std::atan2( y[i] - segmentStartY, x[i] - segmentStartX ) );
889  longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
890  segmentIndex++;
891  currentStraightSegmentLength = 0;
892  segmentStartX = x[i];
893  segmentStartY = y[i];
894  }
895  currentStraightSegmentLength += segmentLengths[i];
896  }
897  distanceToSegment[line->nbPoints - 1] = totalLineLength;
898  straightSegmentLengths << currentStraightSegmentLength;
899  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( std::atan2( y[numberNodes - 1] - segmentStartY, x[numberNodes - 1] - segmentStartX ) );
900  longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
901  const double lineAnchorPoint = totalLineLength * mLF->lineAnchorPercent();
902 
903  if ( totalLineLength < labelWidth )
904  {
905  return 0; //createCandidatesAlongLineNearMidpoint will be more appropriate
906  }
907 
908  const std::size_t candidateTargetCount = maximumLineCandidates();
909  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
910  lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
911 
912  double distanceToEndOfSegment = 0.0;
913  int lastNodeInSegment = 0;
914  // finally, loop through all these straight segments. For each we create candidates along the straight segment.
915  for ( int i = 0; i < straightSegmentLengths.count(); ++i )
916  {
917  currentStraightSegmentLength = straightSegmentLengths.at( i );
918  double currentSegmentAngle = straightSegmentAngles.at( i );
919  lastNodeInSegment = extremeAngleNodes.at( i );
920  double distanceToStartOfSegment = distanceToEndOfSegment;
921  distanceToEndOfSegment = distanceToSegment[ lastNodeInSegment ];
922  double distanceToCenterOfSegment = 0.5 * ( distanceToEndOfSegment + distanceToStartOfSegment );
923 
924  if ( currentStraightSegmentLength < labelWidth )
925  // can't fit a label on here
926  continue;
927 
928  double currentDistanceAlongLine = distanceToStartOfSegment;
929  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
930  double candidateLength = 0.0;
931  double cost = 0.0;
932  double angle = 0.0;
933  double beta = 0.0;
934 
935  //calculate some cost penalties
936  double segmentCost = 1.0 - ( distanceToEndOfSegment - distanceToStartOfSegment ) / longestSegmentLength; // 0 -> 1 (lower for longer segments)
937  double segmentAngleCost = 1 - std::fabs( std::fmod( currentSegmentAngle, M_PI ) - M_PI_2 ) / M_PI_2; // 0 -> 1, lower for more horizontal segments
938 
939  while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
940  {
941  if ( pal->isCanceled() )
942  {
943  return lPos.size();
944  }
945 
946  // calculate positions along linestring corresponding to start and end of current label candidate
947  line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
948  line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
949 
950  candidateLength = std::sqrt( ( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
951 
952 
953  // LOTS OF DIFFERENT COSTS TO BALANCE HERE - feel free to tweak these, but please add a unit test
954  // which covers the situation you are adjusting for (e.g., "given equal length lines, choose the more horizontal line")
955 
956  cost = candidateLength / labelWidth;
957  if ( cost > 0.98 )
958  cost = 0.0001;
959  else
960  {
961  // jaggy line has a greater cost
962  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
963  }
964 
965  // penalize positions which are further from the straight segments's midpoint
966  double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
967  const bool placementIsFlexible = mLF->lineAnchorPercent() > 0.1 && mLF->lineAnchorPercent() < 0.9;
968  if ( placementIsFlexible )
969  {
970  // only apply this if labels are being placed toward the center of overall lines -- otherwise it messes with the distance from anchor cost
971  double costCenter = 2 * std::fabs( labelCenter - distanceToCenterOfSegment ) / ( distanceToEndOfSegment - distanceToStartOfSegment ); // 0 -> 1
972  cost += costCenter * 0.0005; // < 0, 0.0005 >
973  }
974 
975  if ( !closedLine )
976  {
977  // penalize positions which are further from absolute center of whole linestring
978  // this only applies to non closed linestrings, since the middle of a closed linestring is effectively arbitrary
979  // and irrelevant to labeling
980  double costLineCenter = 2 * std::fabs( labelCenter - lineAnchorPoint ) / totalLineLength; // 0 -> 1
981  cost += costLineCenter * 0.0005; // < 0, 0.0005 >
982  }
983 
984  if ( placementIsFlexible )
985  {
986  cost += segmentCost * 0.0005; // prefer labels on longer straight segments
987  cost += segmentAngleCost * 0.0001; // prefer more horizontal segments, but this is less important than length considerations
988  }
989 
990  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
991  {
992  angle = 0.0;
993  }
994  else
995  angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
996 
997  labelWidth = getLabelWidth( angle );
998  labelHeight = getLabelHeight( angle );
999  beta = angle + M_PI_2;
1000 
1002  {
1003  // find out whether the line direction for this candidate is from right to left
1004  bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1005  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
1006  bool reversed = ( ( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) ? isRightToLeft : false );
1007  bool aboveLine = ( !reversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) ) || ( reversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) );
1008  bool belowLine = ( !reversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) ) || ( reversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) );
1009 
1010  if ( belowLine )
1011  {
1012  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1013  {
1014  const double candidateCost = cost + ( reversed ? 0 : 0.001 );
1015  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1016  }
1017  }
1018  if ( aboveLine )
1019  {
1020  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
1021  {
1022  const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements
1023  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1024  }
1025  }
1027  {
1028  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1029  {
1030  const double candidateCost = cost + 0.002;
1031  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1032  }
1033  }
1034  }
1036  {
1037  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this, false, LabelPosition::QuadrantOver ) ); // Line
1038  }
1039  else
1040  {
1041  // an invalid arrangement?
1042  }
1043 
1044  currentDistanceAlongLine += lineStepDistance;
1045  }
1046  }
1047 
1048  return lPos.size();
1049 }
1050 
1051 std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, double initialCost, Pal *pal )
1052 {
1053  double distanceLineToLabel = getLabelDistance();
1054 
1055  double labelWidth = getLabelWidth();
1056  double labelHeight = getLabelHeight();
1057 
1058  double angle;
1059  double cost;
1060 
1061  QgsLabeling::LinePlacementFlags flags = mLF->arrangementFlags();
1062  if ( flags == 0 )
1063  flags = QgsLabeling::LinePlacementFlag::OnLine; // default flag
1064 
1065  PointSet *line = mapShape;
1066  int nbPoints = line->nbPoints;
1067  std::vector< double > &x = line->x;
1068  std::vector< double > &y = line->y;
1069 
1070  std::vector< double > segmentLengths( nbPoints - 1 ); // segments lengths distance bw pt[i] && pt[i+1]
1071  std::vector< double >distanceToSegment( nbPoints ); // absolute distance bw pt[0] and pt[i] along the line
1072 
1073  double totalLineLength = 0.0; // line length
1074  for ( int i = 0; i < line->nbPoints - 1; i++ )
1075  {
1076  if ( i == 0 )
1077  distanceToSegment[i] = 0;
1078  else
1079  distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1080 
1081  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i + 1], y[i + 1] );
1082  totalLineLength += segmentLengths[i];
1083  }
1084  distanceToSegment[line->nbPoints - 1] = totalLineLength;
1085 
1086  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
1087  double currentDistanceAlongLine = 0;
1088 
1089  const std::size_t candidateTargetCount = maximumLineCandidates();
1090 
1091  if ( totalLineLength > labelWidth )
1092  {
1093  lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1094  }
1095  else if ( !line->isClosed() ) // line length < label width => centering label position
1096  {
1097  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
1098  lineStepDistance = -1;
1099  totalLineLength = labelWidth;
1100  }
1101  else
1102  {
1103  // closed line, not long enough for label => no candidates!
1104  currentDistanceAlongLine = std::numeric_limits< double >::max();
1105  }
1106 
1107  const double lineAnchorPoint = totalLineLength * std::min( 0.99, mLF->lineAnchorPercent() ); // don't actually go **all** the way to end of line, just very close to!
1108 
1109  switch ( mLF->lineAnchorType() )
1110  {
1112  break;
1113 
1115  currentDistanceAlongLine = std::min( lineAnchorPoint, totalLineLength * 0.99 - labelWidth );
1116  lineStepDistance = -1;
1117  break;
1118  }
1119 
1120  double candidateLength;
1121  double beta;
1122  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1123  int i = 0;
1124  while ( currentDistanceAlongLine <= totalLineLength - labelWidth || mLF->lineAnchorType() == QgsLabelLineSettings::AnchorType::Strict )
1125  {
1126  if ( pal->isCanceled() )
1127  {
1128  return lPos.size();
1129  }
1130 
1131  // calculate positions along linestring corresponding to start and end of current label candidate
1132  line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1133  line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1134 
1135  if ( currentDistanceAlongLine < 0 )
1136  {
1137  // label is bigger than line, use whole available line
1138  candidateLength = std::sqrt( ( x[nbPoints - 1] - x[0] ) * ( x[nbPoints - 1] - x[0] )
1139  + ( y[nbPoints - 1] - y[0] ) * ( y[nbPoints - 1] - y[0] ) );
1140  }
1141  else
1142  {
1143  candidateLength = std::sqrt( ( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
1144  }
1145 
1146  cost = candidateLength / labelWidth;
1147  if ( cost > 0.98 )
1148  cost = 0.0001;
1149  else
1150  {
1151  // jaggy line has a greater cost
1152  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
1153  }
1154 
1155  // penalize positions which are further from the line's anchor point
1156  double costCenter = std::fabs( lineAnchorPoint - ( currentDistanceAlongLine + labelWidth / 2 ) ) / totalLineLength; // <0, 0.5>
1157  cost += costCenter / 1000; // < 0, 0.0005 >
1158  cost += initialCost;
1159 
1160  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
1161  {
1162  angle = 0.0;
1163  }
1164  else
1165  angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1166 
1167  labelWidth = getLabelWidth( angle );
1168  labelHeight = getLabelHeight( angle );
1169  beta = angle + M_PI_2;
1170 
1172  {
1173  // find out whether the line direction for this candidate is from right to left
1174  bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1175  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
1176  bool reversed = ( ( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) ? isRightToLeft : false );
1177  bool aboveLine = ( !reversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) ) || ( reversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) );
1178  bool belowLine = ( !reversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) ) || ( reversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) );
1179 
1180  if ( aboveLine )
1181  {
1182  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
1183  {
1184  const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements
1185  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1186  }
1187  }
1188  if ( belowLine )
1189  {
1190  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1191  {
1192  const double candidateCost = cost + ( !reversed ? 0.001 : 0 );
1193  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1194  }
1195  }
1197  {
1198  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1199  {
1200  const double candidateCost = cost + 0.002;
1201  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft, LabelPosition::QuadrantOver ) ); // Line
1202  }
1203  }
1204  }
1206  {
1207  lPos.emplace_back( qgis::make_unique< LabelPosition >( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this, false, LabelPosition::QuadrantOver ) ); // Line
1208  }
1209  else
1210  {
1211  // an invalid arrangement?
1212  }
1213 
1214  currentDistanceAlongLine += lineStepDistance;
1215 
1216  i++;
1217 
1218  if ( lineStepDistance < 0 )
1219  break;
1220  }
1221 
1222  return lPos.size();
1223 }
1224 
1225 
1226 std::unique_ptr< LabelPosition > FeaturePart::curvedPlacementAtOffset( PointSet *path_positions, double *path_distances, int &orientation, const double offsetAlongLine, bool &reversed, bool &flip, bool applyAngleConstraints )
1227 {
1228  double offsetAlongSegment = offsetAlongLine;
1229  int index = 1;
1230  // Find index of segment corresponding to starting offset
1231  while ( index < path_positions->nbPoints && offsetAlongSegment > path_distances[index] )
1232  {
1233  offsetAlongSegment -= path_distances[index];
1234  index += 1;
1235  }
1236  if ( index >= path_positions->nbPoints )
1237  {
1238  return nullptr;
1239  }
1240 
1241  LabelInfo *li = mLF->curvedLabelInfo();
1242 
1243  double string_height = li->label_height;
1244 
1245  const double segment_length = path_distances[index];
1246  if ( qgsDoubleNear( segment_length, 0.0 ) )
1247  {
1248  // Not allowed to place across on 0 length segments or discontinuities
1249  return nullptr;
1250  }
1251 
1252  if ( orientation == 0 ) // Must be map orientation
1253  {
1254  // Calculate the orientation based on the angle of the path segment under consideration
1255 
1256  double _distance = offsetAlongSegment;
1257  int endindex = index;
1258 
1259  double startLabelX = 0;
1260  double startLabelY = 0;
1261  double endLabelX = 0;
1262  double endLabelY = 0;
1263  for ( int i = 0; i < li->char_num; i++ )
1264  {
1265  LabelInfo::CharacterInfo &ci = li->char_info[i];
1266  double characterStartX, characterStartY;
1267  if ( !nextCharPosition( ci.width, path_distances[endindex], path_positions, endindex, _distance, characterStartX, characterStartY, endLabelX, endLabelY ) )
1268  {
1269  return nullptr;
1270  }
1271  if ( i == 0 )
1272  {
1273  startLabelX = characterStartX;
1274  startLabelY = characterStartY;
1275  }
1276  }
1277 
1278  // Determine the angle of the path segment under consideration
1279  double dx = endLabelX - startLabelX;
1280  double dy = endLabelY - startLabelY;
1281  const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
1282 
1283  bool isRightToLeft = ( lineAngle > 90 || lineAngle < -90 );
1284  reversed = isRightToLeft;
1285  orientation = isRightToLeft ? -1 : 1;
1286  }
1287 
1288  if ( !showUprightLabels() )
1289  {
1290  if ( orientation < 0 )
1291  {
1292  flip = true; // Report to the caller, that the orientation is flipped
1293  reversed = !reversed;
1294  orientation = 1;
1295  }
1296  }
1297 
1298  std::unique_ptr< LabelPosition > slp;
1299  LabelPosition *slp_tmp = nullptr;
1300 
1301  double old_x = path_positions->x[index - 1];
1302  double old_y = path_positions->y[index - 1];
1303 
1304  double new_x = path_positions->x[index];
1305  double new_y = path_positions->y[index];
1306 
1307  double dx = new_x - old_x;
1308  double dy = new_y - old_y;
1309 
1310  double angle = std::atan2( -dy, dx );
1311 
1312  for ( int i = 0; i < li->char_num; i++ )
1313  {
1314  double last_character_angle = angle;
1315 
1316  // grab the next character according to the orientation
1317  LabelInfo::CharacterInfo &ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num - i - 1] );
1318  if ( qgsDoubleNear( ci.width, 0.0 ) )
1319  // Certain scripts rely on zero-width character, skip those to prevent failure (see #15801)
1320  continue;
1321 
1322  double start_x, start_y, end_x, end_y;
1323  if ( !nextCharPosition( ci.width, path_distances[index], path_positions, index, offsetAlongSegment, start_x, start_y, end_x, end_y ) )
1324  {
1325  return nullptr;
1326  }
1327 
1328  // Calculate angle from the start of the character to the end based on start_/end_ position
1329  angle = std::atan2( start_y - end_y, end_x - start_x );
1330 
1331  // Test last_character_angle vs angle
1332  // since our rendering angle has changed then check against our
1333  // max allowable angle change.
1334  double angle_delta = last_character_angle - angle;
1335  // normalise between -180 and 180
1336  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1337  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1338  if ( applyAngleConstraints && ( ( li->max_char_angle_inside > 0 && angle_delta > 0
1339  && angle_delta > li->max_char_angle_inside * ( M_PI / 180 ) )
1340  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1341  && angle_delta < li->max_char_angle_outside * ( M_PI / 180 ) ) ) )
1342  {
1343  return nullptr;
1344  }
1345 
1346  // Shift the character downwards since the draw position is specified at the baseline
1347  // and we're calculating the mean line here
1348  double dist = 0.9 * li->label_height / 2;
1349  if ( orientation < 0 )
1350  {
1351  dist = -dist;
1352  flip = true;
1353  }
1354  start_x += dist * std::cos( angle + M_PI_2 );
1355  start_y -= dist * std::sin( angle + M_PI_2 );
1356 
1357  double render_angle = angle;
1358 
1359  double render_x = start_x;
1360  double render_y = start_y;
1361 
1362  // Center the text on the line
1363  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1364  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1365 
1366  if ( orientation < 0 )
1367  {
1368  // rotate in place
1369  render_x += ci.width * std::cos( render_angle ); //- (string_height-2)*sin(render_angle);
1370  render_y -= ci.width * std::sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1371  render_angle += M_PI;
1372  }
1373 
1374  std::unique_ptr< LabelPosition > tmp = qgis::make_unique< LabelPosition >( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this, false, LabelPosition::QuadrantOver );
1375  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1376  LabelPosition *next = tmp.get();
1377  if ( !slp )
1378  slp = std::move( tmp );
1379  else
1380  slp_tmp->setNextPart( std::move( tmp ) );
1381  slp_tmp = next;
1382 
1383  // Normalise to 0 <= angle < 2PI
1384  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1385  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1386 
1387  if ( render_angle > M_PI_2 && render_angle < 1.5 * M_PI )
1389  }
1390 
1391  return slp;
1392 }
1393 
1394 static std::unique_ptr< LabelPosition > _createCurvedCandidate( LabelPosition *lp, double angle, double dist )
1395 {
1396  std::unique_ptr< LabelPosition > newLp = qgis::make_unique< LabelPosition >( *lp );
1397  newLp->offsetPosition( dist * std::cos( angle + M_PI_2 ), dist * std::sin( angle + M_PI_2 ) );
1398  return newLp;
1399 }
1400 
1401 std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal )
1402 {
1403  LabelInfo *li = mLF->curvedLabelInfo();
1404 
1405  // label info must be present
1406  if ( !li || li->char_num == 0 )
1407  return 0;
1408 
1409  // TODO - we may need an explicit penalty for overhanging labels. Currently, they are penalized just because they
1410  // are further from the line center, so non-overhanding placements are picked where possible.
1411 
1412  double totalCharacterWidth = 0;
1413  for ( int i = 0; i < li->char_num; ++i )
1414  totalCharacterWidth += li->char_info[ i ].width;
1415 
1416  std::unique_ptr< PointSet > expanded;
1417  double shapeLength = mapShape->length();
1418 
1419  if ( totalRepeats() > 1 )
1420  allowOverrun = false;
1421 
1422  // label overrun should NEVER exceed the label length (or labels would sit off in space).
1423  // in fact, let's require that a minimum of 5% of the label text has to sit on the feature,
1424  // as we don't want a label sitting right at the start or end corner of a line
1425  double overrun = std::min( mLF->overrunDistance(), totalCharacterWidth * 0.95 );
1426  if ( totalCharacterWidth > shapeLength )
1427  {
1428  if ( !allowOverrun || shapeLength < totalCharacterWidth - 2 * overrun )
1429  {
1430  // label doesn't fit on this line, don't waste time trying to make candidates
1431  return 0;
1432  }
1433  }
1434 
1435  if ( allowOverrun && overrun > 0 )
1436  {
1437  // expand out line on either side to fit label
1438  expanded = mapShape->clone();
1439  expanded->extendLineByDistance( overrun, overrun, mLF->overrunSmoothDistance() );
1440  mapShape = expanded.get();
1441  shapeLength = mapShape->length();
1442  }
1443 
1444  // distance calculation
1445  std::unique_ptr< double [] > path_distances = qgis::make_unique<double[]>( mapShape->nbPoints );
1446  double total_distance = 0;
1447  double old_x = -1.0, old_y = -1.0;
1448  for ( int i = 0; i < mapShape->nbPoints; i++ )
1449  {
1450  if ( i == 0 )
1451  path_distances[i] = 0;
1452  else
1453  path_distances[i] = std::sqrt( std::pow( old_x - mapShape->x[i], 2 ) + std::pow( old_y - mapShape->y[i], 2 ) );
1454  old_x = mapShape->x[i];
1455  old_y = mapShape->y[i];
1456 
1457  total_distance += path_distances[i];
1458  }
1459 
1460  if ( qgsDoubleNear( total_distance, 0.0 ) )
1461  {
1462  return 0;
1463  }
1464 
1465  const double lineAnchorPoint = total_distance * mLF->lineAnchorPercent();
1466 
1467  if ( pal->isCanceled() )
1468  return 0;
1469 
1470  std::vector< std::unique_ptr< LabelPosition >> positions;
1471  const std::size_t candidateTargetCount = maximumLineCandidates();
1472  double delta = std::max( li->label_height / 6, total_distance / candidateTargetCount );
1473 
1474  QgsLabeling::LinePlacementFlags flags = mLF->arrangementFlags();
1475  if ( flags == 0 )
1476  flags = QgsLabeling::LinePlacementFlag::OnLine; // default flag
1477 
1478  // generate curved labels
1479  double distanceAlongLineToStartCandidate = 0;
1480  bool singleCandidateOnly = false;
1481  switch ( mLF->lineAnchorType() )
1482  {
1484  break;
1485 
1487  distanceAlongLineToStartCandidate = std::min( lineAnchorPoint, total_distance * 0.99 - getLabelWidth() );
1488  singleCandidateOnly = true;
1489  break;
1490  }
1491 
1492  for ( ; distanceAlongLineToStartCandidate <= total_distance; distanceAlongLineToStartCandidate += delta )
1493  {
1494  bool flip = false;
1495  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1496  bool reversed = false;
1497 
1498  if ( pal->isCanceled() )
1499  return 0;
1500 
1501  // an orientation of 0 means try both orientations and choose the best
1502  int orientation = 0;
1504  {
1505  //... but if we are using line orientation flags, then we can only accept a single orientation,
1506  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1507  orientation = 1;
1508  }
1509 
1510  std::unique_ptr< LabelPosition > slp = curvedPlacementAtOffset( mapShape, path_distances.get(), orientation, distanceAlongLineToStartCandidate, reversed, flip, !singleCandidateOnly );
1511  if ( !slp )
1512  continue;
1513 
1514  // If we placed too many characters upside down
1515  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1516  {
1517  // if labels should be shown upright then retry with the opposite orientation
1518  if ( ( showUprightLabels() && !flip ) )
1519  {
1520  orientation = -orientation;
1521  slp = curvedPlacementAtOffset( mapShape, path_distances.get(), orientation, distanceAlongLineToStartCandidate, reversed, flip, !singleCandidateOnly );
1522  }
1523  }
1524  if ( !slp )
1525  continue;
1526 
1527  // evaluate cost
1528  double angle_diff = 0.0, angle_last = 0.0, diff;
1529  LabelPosition *tmp = slp.get();
1530  double sin_avg = 0, cos_avg = 0;
1531  while ( tmp )
1532  {
1533  if ( tmp != slp.get() ) // not first?
1534  {
1535  diff = std::fabs( tmp->getAlpha() - angle_last );
1536  if ( diff > 2 * M_PI ) diff -= 2 * M_PI;
1537  diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1538  angle_diff += diff;
1539  }
1540 
1541  sin_avg += std::sin( tmp->getAlpha() );
1542  cos_avg += std::cos( tmp->getAlpha() );
1543  angle_last = tmp->getAlpha();
1544  tmp = tmp->nextPart();
1545  }
1546 
1547  // if anchor placement is towards start or end of line, we need to slightly tweak the costs to ensure that the
1548  // anchor weighting is sufficient to push labels towards start/end
1549  const bool anchorIsFlexiblePlacement = !singleCandidateOnly && mLF->lineAnchorPercent() > 0.1 && mLF->lineAnchorPercent() < 0.9;
1550  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1551  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1552  if ( cost < 0.0001 )
1553  cost = 0.0001;
1554 
1555  // penalize positions which are further from the line's anchor point
1556  double labelCenter = distanceAlongLineToStartCandidate + getLabelWidth() / 2;
1557  double costCenter = std::fabs( lineAnchorPoint - labelCenter ) / total_distance; // <0, 0.5>
1558  cost += costCenter / ( anchorIsFlexiblePlacement ? 100 : 10 ); // < 0, 0.005 >, or <0, 0.05> if preferring placement close to start/end of line
1559  slp->setCost( cost );
1560 
1561  // average angle is calculated with respect to periodicity of angles
1562  double angle_avg = std::atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1563  bool localreversed = flip ? !reversed : reversed;
1564  // displacement - we loop through 3 times, generating above, online then below line placements successively
1565  for ( int i = 0; i <= 2; ++i )
1566  {
1567  std::unique_ptr< LabelPosition > p;
1568  if ( i == 0 && ( ( !localreversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) ) || ( localreversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) ) ) )
1569  p = _createCurvedCandidate( slp.get(), angle_avg, mLF->distLabel() + li->label_height / 2 );
1570  if ( i == 1 && flags & QgsLabeling::LinePlacementFlag::OnLine )
1571  {
1572  p = _createCurvedCandidate( slp.get(), angle_avg, 0 );
1573  p->setCost( p->cost() + 0.002 );
1574  }
1575  if ( i == 2 && ( ( !localreversed && ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) ) || ( localreversed && ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) ) ) )
1576  {
1577  p = _createCurvedCandidate( slp.get(), angle_avg, -li->label_height / 2 - mLF->distLabel() );
1578  p->setCost( p->cost() + 0.001 );
1579  }
1580 
1581  if ( p && mLF->permissibleZonePrepared() )
1582  {
1583  bool within = true;
1584  LabelPosition *currentPos = p.get();
1585  while ( within && currentPos )
1586  {
1587  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1588  currentPos = currentPos->nextPart();
1589  }
1590  if ( !within )
1591  {
1592  p.reset();
1593  }
1594  }
1595 
1596  if ( p )
1597  positions.emplace_back( std::move( p ) );
1598  }
1599  if ( singleCandidateOnly )
1600  break;
1601  }
1602 
1603  for ( std::unique_ptr< LabelPosition > &pos : positions )
1604  {
1605  lPos.emplace_back( std::move( pos ) );
1606  }
1607 
1608  return positions.size();
1609 }
1610 
1611 /*
1612  * seg 2
1613  * pt3 ____________pt2
1614  * ¦ ¦
1615  * ¦ ¦
1616  * seg 3 ¦ BBOX ¦ seg 1
1617  * ¦ ¦
1618  * ¦____________¦
1619  * pt0 seg 0 pt1
1620  *
1621  */
1622 
1623 std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal )
1624 {
1625  double labelWidth = getLabelWidth();
1626  double labelHeight = getLabelHeight();
1627 
1628  const std::size_t maxPolygonCandidates = mLF->layer()->maximumPolygonLabelCandidates();
1629  const std::size_t targetPolygonCandidates = maxPolygonCandidates > 0 ? std::min( maxPolygonCandidates, static_cast< std::size_t>( std::ceil( mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * area() ) ) )
1630  : 0;
1631 
1632  QLinkedList<PointSet *> shapes_toProcess;
1633  QLinkedList<PointSet *> shapes_final;
1634  const double totalArea = area();
1635 
1636  mapShape->parent = nullptr;
1637 
1638  if ( pal->isCanceled() )
1639  return 0;
1640 
1641  shapes_toProcess.append( mapShape );
1642 
1643  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1644 
1645  std::size_t nbp = 0;
1646 
1647  if ( !shapes_final.isEmpty() )
1648  {
1649  int id = 0; // ids for candidates
1650  double dlx, dly; // delta from label center and bottom-left corner
1651  double alpha = 0.0; // rotation for the label
1652  double px, py;
1653 
1654  double beta;
1655  double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1656  double rx, ry;
1657  std::vector< CHullBox > boxes;
1658  boxes.reserve( shapes_final.size() );
1659 
1660  // Compute bounding box foreach finalShape
1661  while ( !shapes_final.isEmpty() )
1662  {
1663  PointSet *shape = shapes_final.takeFirst();
1664  boxes.emplace_back( shape->compute_chull_bbox() );
1665 
1666  if ( shape->parent )
1667  delete shape;
1668  }
1669 
1670  if ( pal->isCanceled() )
1671  return 0;
1672 
1673  double densityX = 1.0 / std::sqrt( mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() );
1674  double densityY = densityX;
1675  int numTry = 0;
1676 
1677  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1678  //then use a smaller limit for number of iterations
1679  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1680 
1681  std::size_t numberCandidatesGenerated = 0;
1682 
1683  do
1684  {
1685  for ( CHullBox &box : boxes )
1686  {
1687  // there is two possibilities here:
1688  // 1. no maximum candidates for polygon setting is in effect (i.e. maxPolygonCandidates == 0). In that case,
1689  // we base our dx/dy on the current maximumPolygonCandidatesPerMapUnitSquared value. That should give us the desired
1690  // density of candidates straight up. Easy!
1691  // 2. a maximum candidate setting IS in effect. In that case, we want to generate a good initial estimate for dx/dy
1692  // which gives us a good spatial coverage of the polygon while roughly matching the desired maximum number of candidates.
1693  // If dx/dy is too small, then too many candidates will be generated, which is both slow AND results in poor coverage of the
1694  // polygon (after culling candidates to the max number, only those clustered around the polygon's pole of inaccessibility
1695  // will remain).
1696  double dx = densityX;
1697  double dy = densityY;
1698  if ( numTry == 0 && maxPolygonCandidates > 0 )
1699  {
1700  // scale maxPolygonCandidates for just this convex hull
1701  const double boxArea = box.width * box.length;
1702  double maxThisBox = targetPolygonCandidates * boxArea / totalArea;
1703  dx = std::max( dx, std::sqrt( boxArea / maxThisBox ) * 0.8 );
1704  dy = dx;
1705  }
1706 
1707  if ( pal->isCanceled() )
1708  return numberCandidatesGenerated;
1709 
1710  if ( ( box.length * box.width ) > ( xmax - xmin ) * ( ymax - ymin ) * 5 )
1711  {
1712  // Very Large BBOX (should never occur)
1713  continue;
1714  }
1715 
1717  {
1718  //check width/height of bbox is sufficient for label
1719  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1720  mLF->permissibleZone().boundingBox().height() < labelHeight )
1721  {
1722  //no way label can fit in this box, skip it
1723  continue;
1724  }
1725  }
1726 
1727  bool enoughPlace = false;
1729  {
1730  enoughPlace = true;
1731  px = ( box.x[0] + box.x[2] ) / 2 - labelWidth;
1732  py = ( box.y[0] + box.y[2] ) / 2 - labelHeight;
1733  int i, j;
1734 
1735  // Virtual label: center on bbox center, label size = 2x original size
1736  // alpha = 0.
1737  // If all corner are in bbox then place candidates horizontaly
1738  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1739  {
1740  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1741  {
1742  if ( !mapShape->containsPoint( rx, ry ) )
1743  {
1744  enoughPlace = false;
1745  break;
1746  }
1747  }
1748  if ( !enoughPlace )
1749  {
1750  break;
1751  }
1752  }
1753 
1754  } // arrangement== FREE ?
1755 
1756  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1757  {
1758  alpha = 0.0; // HORIZ
1759  }
1760  else if ( box.length > 1.5 * labelWidth && box.width > 1.5 * labelWidth )
1761  {
1762  if ( box.alpha <= M_PI_4 )
1763  {
1764  alpha = box.alpha;
1765  }
1766  else
1767  {
1768  alpha = box.alpha - M_PI_2;
1769  }
1770  }
1771  else if ( box.length > box.width )
1772  {
1773  alpha = box.alpha - M_PI_2;
1774  }
1775  else
1776  {
1777  alpha = box.alpha;
1778  }
1779 
1780  beta = std::atan2( labelHeight, labelWidth ) + alpha;
1781 
1782 
1783  //alpha = box->alpha;
1784 
1785  // delta from label center and down-left corner
1786  dlx = std::cos( beta ) * diago;
1787  dly = std::sin( beta ) * diago;
1788 
1789  double px0 = box.width / 2.0;
1790  double py0 = box.length / 2.0;
1791 
1792  px0 -= std::ceil( px0 / dx ) * dx;
1793  py0 -= std::ceil( py0 / dy ) * dy;
1794 
1795  for ( px = px0; px <= box.width; px += dx )
1796  {
1797  if ( pal->isCanceled() )
1798  break;
1799 
1800  for ( py = py0; py <= box.length; py += dy )
1801  {
1802 
1803  rx = std::cos( box.alpha ) * px + std::cos( box.alpha - M_PI_2 ) * py;
1804  ry = std::sin( box.alpha ) * px + std::sin( box.alpha - M_PI_2 ) * py;
1805 
1806  rx += box.x[0];
1807  ry += box.y[0];
1808 
1809  if ( mLF->permissibleZonePrepared() )
1810  {
1811  if ( GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha ) )
1812  {
1813  // cost is set to minimal value, evaluated later
1814  lPos.emplace_back( qgis::make_unique< LabelPosition >( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this, false, LabelPosition::QuadrantOver ) );
1815  numberCandidatesGenerated++;
1816  }
1817  }
1818  else
1819  {
1820  // TODO - this should be an intersection test, not just a contains test of the candidate centroid
1821  // because in some cases we would want to allow candidates which mostly overlap the polygon even though
1822  // their centroid doesn't overlap (e.g. a "U" shaped polygon)
1823  // but the bugs noted in CostCalculator currently prevent this
1824  if ( mapShape->containsPoint( rx, ry ) )
1825  {
1826  std::unique_ptr< LabelPosition > potentialCandidate = qgis::make_unique< LabelPosition >( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this, false, LabelPosition::QuadrantOver );
1827  // cost is set to minimal value, evaluated later
1828  lPos.emplace_back( std::move( potentialCandidate ) );
1829  numberCandidatesGenerated++;
1830  }
1831  }
1832  }
1833  }
1834  } // forall box
1835 
1836  nbp = numberCandidatesGenerated;
1837  if ( maxPolygonCandidates > 0 && nbp < targetPolygonCandidates )
1838  {
1839  densityX /= 2;
1840  densityY /= 2;
1841  numTry++;
1842  }
1843  else
1844  {
1845  break;
1846  }
1847  }
1848  while ( numTry < maxTry );
1849 
1850  nbp = numberCandidatesGenerated;
1851  }
1852  else
1853  {
1854  nbp = 0;
1855  }
1856 
1857  return nbp;
1858 }
1859 
1860 std::size_t FeaturePart::createCandidatesOutsidePolygon( std::vector<std::unique_ptr<LabelPosition> > &lPos, Pal *pal )
1861 {
1862  // calculate distance between horizontal lines
1863  const std::size_t maxPolygonCandidates = mLF->layer()->maximumPolygonLabelCandidates();
1864  std::size_t candidatesCreated = 0;
1865 
1866  double labelWidth = getLabelWidth();
1867  double labelHeight = getLabelHeight();
1868  double distanceToLabel = getLabelDistance();
1869  const QgsMargins &visualMargin = mLF->visualMargin();
1870 
1871  /*
1872  * From Rylov & Reimer (2016) "A practical algorithm for the external annotation of area features":
1873  *
1874  * The list of rules adapted to the
1875  * needs of externally labelling areal features is as follows:
1876  * R1. Labels should be placed horizontally.
1877  * R2. Label should be placed entirely outside at some
1878  * distance from the area feature.
1879  * R3. Name should not cross the boundary of its area
1880  * feature.
1881  * R4. The name should be placed in way that takes into
1882  * account the shape of the feature by achieving a
1883  * balance between the feature and its name, emphasizing their relationship.
1884  * R5. The lettering to the right and slightly above the
1885  * symbol is prioritized.
1886  *
1887  * In the following subsections we utilize four of the five rules
1888  * for two subtasks of label placement, namely, for candidate
1889  * positions generation (R1, R2, and R3) and for measuring their
1890  * ‘goodness’ (R4). The rule R5 is applicable only in the case when
1891  * the area of a polygonal feature is small and the feature can be
1892  * treated and labelled as a point-feature
1893  */
1894 
1895  /*
1896  * QGIS approach (cite Dawson (2020) if you want ;) )
1897  *
1898  * We differ from the horizontal sweep line approach described by Rylov & Reimer and instead
1899  * rely on just generating a set of points at regular intervals along the boundary of the polygon (exterior ring).
1900  *
1901  * In practice, this generates similar results as Rylov & Reimer, but has the additional benefits that:
1902  * 1. It avoids the need to calculate intersections between the sweep line and the polygon
1903  * 2. For horizontal or near horizontal segments, Rylov & Reimer propose generating evenly spaced points along
1904  * these segments-- i.e. the same approach as we do for the whole polygon
1905  * 3. It's easier to determine in advance exactly how many candidate positions we'll be generating, and accordingly
1906  * we can easily pick the distance between points along the exterior ring so that the number of positions generated
1907  * matches our target number (targetPolygonCandidates)
1908  */
1909 
1910  // TO consider -- for very small polygons (wrt label size), treat them just like a point feature?
1911 
1912  double cx, cy;
1913  getCentroid( cx, cy, false );
1914 
1915  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
1916 
1917  // be a bit sneaky and only buffer out 50% here, and then do the remaining 50% when we make the label candidate itself.
1918  // this avoids candidates being created immediately over the buffered ring and always intersecting with it...
1919  geos::unique_ptr buffer( GEOSBuffer_r( ctxt, geos(), distanceToLabel * 0.5, 1 ) );
1920  std::unique_ptr< QgsAbstractGeometry> gg( QgsGeos::fromGeos( buffer.get() ) );
1921 
1922  geos::prepared_unique_ptr preparedBuffer( GEOSPrepare_r( ctxt, buffer.get() ) );
1923 
1924  const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( gg.get() );
1925  if ( !poly )
1926  return candidatesCreated;
1927 
1928  const QgsLineString *ring = qgsgeometry_cast< const QgsLineString *>( poly->exteriorRing() );
1929  if ( !ring )
1930  return candidatesCreated;
1931 
1932  // we cheat here -- we don't use the polygon area when calculating the number of candidates, and rather use the perimeter (because that's more relevant,
1933  // i.e a loooooong skinny polygon with small area should still generate a large number of candidates)
1934  const double ringLength = ring->length();
1935  const double circleArea = std::pow( ringLength, 2 ) / ( 4 * M_PI );
1936  const std::size_t candidatesForArea = static_cast< std::size_t>( std::ceil( mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * circleArea ) );
1937  const std::size_t targetPolygonCandidates = std::max( static_cast< std::size_t >( 16 ), maxPolygonCandidates > 0 ? std::min( maxPolygonCandidates, candidatesForArea ) : candidatesForArea );
1938 
1939  // assume each position generates one candidate
1940  const double delta = ringLength / targetPolygonCandidates;
1941  geos::unique_ptr geosPoint;
1942 
1943  const double maxDistCentroidToLabelX = std::max( xmax - cx, cx - xmin ) + distanceToLabel;
1944  const double maxDistCentroidToLabelY = std::max( ymax - cy, cy - ymin ) + distanceToLabel;
1945  const double estimateOfMaxPossibleDistanceCentroidToLabel = std::sqrt( maxDistCentroidToLabelX * maxDistCentroidToLabelX + maxDistCentroidToLabelY * maxDistCentroidToLabelY );
1946 
1947  // Satisfy R1: Labels should be placed horizontally.
1948  const double labelAngle = 0;
1949 
1950  std::size_t i = lPos.size();
1951  auto addCandidate = [&]( double x, double y, QgsPalLayerSettings::PredefinedPointPosition position )
1952  {
1953  double labelX = 0;
1954  double labelY = 0;
1956 
1957  // Satisfy R2: Label should be placed entirely outside at some distance from the area feature.
1958  createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant, x, y, labelWidth, labelHeight, position, distanceToLabel * 0.5, visualMargin, 0, 0 );
1959 
1960  std::unique_ptr< LabelPosition > candidate = qgis::make_unique< LabelPosition >( i, labelX, labelY, labelWidth, labelHeight, labelAngle, 0, this, false, quadrant );
1961  if ( candidate->intersects( preparedBuffer.get() ) )
1962  {
1963  // satisfy R3. Name should not cross the boundary of its area feature.
1964 
1965  // actually, we use the buffered geometry here, because a label shouldn't be closer to the polygon then the minimum distance value
1966  return;
1967  }
1968 
1969  // cost candidates by their distance to the feature's centroid (following Rylov & Reimer)
1970 
1971  // Satisfy R4. The name should be placed in way that takes into
1972  // account the shape of the feature by achieving a
1973  // balance between the feature and its name, emphasizing their relationship.
1974 
1975 
1976  // here we deviate a little from R&R, and instead of just calculating the centroid distance
1977  // to centroid of label, we calculate the distance from the centroid to the nearest point on the label
1978 
1979  const double centroidDistance = candidate->getDistanceToPoint( cx, cy );
1980  const double centroidCost = centroidDistance / estimateOfMaxPossibleDistanceCentroidToLabel;
1981  candidate->setCost( centroidCost );
1982 
1983  lPos.emplace_back( std::move( candidate ) );
1984  candidatesCreated++;
1985  ++i;
1986  };
1987 
1988  ring->visitPointsByRegularDistance( delta, [&]( double x, double y, double, double,
1989  double startSegmentX, double startSegmentY, double, double,
1990  double endSegmentX, double endSegmentY, double, double )
1991  {
1992  // get normal angle for segment
1993  float angle = atan2( static_cast< float >( endSegmentY - startSegmentY ), static_cast< float >( endSegmentX - startSegmentX ) ) * 180 / M_PI;
1994  if ( angle < 0 )
1995  angle += 360;
1996 
1997  // adapted fom Rylov & Reimer figure 9
1998  if ( angle >= 0 && angle <= 5 )
1999  {
2000  addCandidate( x, y, QgsPalLayerSettings::TopMiddle );
2001  addCandidate( x, y, QgsPalLayerSettings::TopLeft );
2002  }
2003  else if ( angle <= 85 )
2004  {
2005  addCandidate( x, y, QgsPalLayerSettings::TopLeft );
2006  }
2007  else if ( angle <= 90 )
2008  {
2009  addCandidate( x, y, QgsPalLayerSettings::TopLeft );
2010  addCandidate( x, y, QgsPalLayerSettings::MiddleLeft );
2011  }
2012 
2013  else if ( angle <= 95 )
2014  {
2015  addCandidate( x, y, QgsPalLayerSettings::MiddleLeft );
2016  addCandidate( x, y, QgsPalLayerSettings::BottomLeft );
2017  }
2018  else if ( angle <= 175 )
2019  {
2020  addCandidate( x, y, QgsPalLayerSettings::BottomLeft );
2021  }
2022  else if ( angle <= 180 )
2023  {
2024  addCandidate( x, y, QgsPalLayerSettings::BottomLeft );
2025  addCandidate( x, y, QgsPalLayerSettings::BottomMiddle );
2026  }
2027 
2028  else if ( angle <= 185 )
2029  {
2030  addCandidate( x, y, QgsPalLayerSettings::BottomMiddle );
2031  addCandidate( x, y, QgsPalLayerSettings::BottomRight );
2032  }
2033  else if ( angle <= 265 )
2034  {
2035  addCandidate( x, y, QgsPalLayerSettings::BottomRight );
2036  }
2037  else if ( angle <= 270 )
2038  {
2039  addCandidate( x, y, QgsPalLayerSettings::BottomRight );
2040  addCandidate( x, y, QgsPalLayerSettings::MiddleRight );
2041  }
2042  else if ( angle <= 275 )
2043  {
2044  addCandidate( x, y, QgsPalLayerSettings::MiddleRight );
2045  addCandidate( x, y, QgsPalLayerSettings::TopRight );
2046  }
2047  else if ( angle <= 355 )
2048  {
2049  addCandidate( x, y, QgsPalLayerSettings::TopRight );
2050  }
2051  else
2052  {
2053  addCandidate( x, y, QgsPalLayerSettings::TopRight );
2054  addCandidate( x, y, QgsPalLayerSettings::TopMiddle );
2055  }
2056 
2057  return !pal->isCanceled();
2058  } );
2059 
2060  return candidatesCreated;
2061 }
2062 
2063 std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates( Pal *pal )
2064 {
2065  std::vector< std::unique_ptr< LabelPosition > > lPos;
2066  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
2067 
2068  if ( mLF->hasFixedPosition() )
2069  {
2070  lPos.emplace_back( qgis::make_unique< LabelPosition> ( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth( angle ), getLabelHeight( angle ), angle, 0.0, this, false, LabelPosition::Quadrant::QuadrantOver ) );
2071  }
2072  else
2073  {
2074  switch ( type )
2075  {
2076  case GEOS_POINT:
2080  createCandidatesOverPoint( x[0], y[0], lPos, angle );
2081  else
2082  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
2083  break;
2084 
2085  case GEOS_LINESTRING:
2088  else if ( mLF->layer()->isCurved() )
2089  createCurvedCandidatesAlongLine( lPos, this, true, pal );
2090  else
2091  createCandidatesAlongLine( lPos, this, true, pal );
2092  break;
2093 
2094  case GEOS_POLYGON:
2095  {
2096  const double labelWidth = getLabelWidth();
2097  const double labelHeight = getLabelHeight();
2098 
2099  const bool allowOutside = mLF->polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
2100  const bool allowInside = mLF->polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
2101  //check width/height of bbox is sufficient for label
2102 
2103  if ( ( allowOutside && !allowInside ) || ( mLF->layer()->arrangement() == QgsPalLayerSettings::OutsidePolygons ) )
2104  {
2105  // only allowed to place outside of polygon
2107  }
2108  else if ( allowOutside && ( std::fabs( xmax - xmin ) < labelWidth ||
2109  std::fabs( ymax - ymin ) < labelHeight ) )
2110  {
2111  //no way label can fit in this polygon -- shortcut and only place label outside
2113  }
2114  else
2115  {
2116  std::size_t created = 0;
2117  if ( allowInside )
2118  {
2119  switch ( mLF->layer()->arrangement() )
2120  {
2122  {
2123  double cx, cy;
2124  getCentroid( cx, cy, mLF->layer()->centroidInside() );
2125  if ( qgsDoubleNear( mLF->distLabel(), 0.0 ) )
2126  created += createCandidateCenteredOverPoint( cx, cy, lPos, angle );
2127  created += createCandidatesAroundPoint( cx, cy, lPos, angle );
2128  break;
2129  }
2131  {
2132  double cx, cy;
2133  getCentroid( cx, cy, mLF->layer()->centroidInside() );
2134  created += createCandidatesOverPoint( cx, cy, lPos, angle );
2135  break;
2136  }
2138  created += createCandidatesAlongLine( lPos, this, false, pal );
2139  break;
2141  created += createCurvedCandidatesAlongLine( lPos, this, false, pal );
2142  break;
2143  default:
2144  created += createCandidatesForPolygon( lPos, this, pal );
2145  break;
2146  }
2147  }
2148 
2149  if ( allowOutside )
2150  {
2151  // add fallback for labels outside the polygon
2153 
2154  if ( created > 0 )
2155  {
2156  // TODO (maybe) increase cost for outside placements (i.e. positions at indices >= created)?
2157  // From my initial testing this doesn't seem necessary
2158  }
2159  }
2160  }
2161  }
2162  }
2163  }
2164 
2165  return lPos;
2166 }
2167 
2168 void FeaturePart::addSizePenalty( std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4] )
2169 {
2170  if ( !mGeos )
2171  createGeosGeom();
2172 
2173  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
2174  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
2175 
2176  double sizeCost = 0;
2177  if ( geomType == GEOS_LINESTRING )
2178  {
2179  const double l = length();
2180  if ( l <= 0 )
2181  return; // failed to calculate length
2182  double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
2183  if ( l >= bbox_length / 4 )
2184  return; // the line is longer than quarter of height or width - don't penalize it
2185 
2186  sizeCost = 1 - ( l / ( bbox_length / 4 ) ); // < 0,1 >
2187  }
2188  else if ( geomType == GEOS_POLYGON )
2189  {
2190  const double a = area();
2191  if ( a <= 0 )
2192  return;
2193  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
2194  if ( a >= bbox_area / 16 )
2195  return; // covers more than 1/16 of our view - don't penalize it
2196 
2197  sizeCost = 1 - ( a / ( bbox_area / 16 ) ); // < 0, 1 >
2198  }
2199  else
2200  return; // no size penalty for points
2201 
2202 // apply the penalty
2203  for ( std::unique_ptr< LabelPosition > &pos : lPos )
2204  {
2205  pos->setCost( pos->cost() + sizeCost / 100 );
2206  }
2207 }
2208 
2210 {
2211  if ( !p2->mGeos )
2212  p2->createGeosGeom();
2213 
2214  try
2215  {
2216  return ( GEOSPreparedTouches_r( QgsGeos::getGEOSHandler(), preparedGeom(), p2->mGeos ) == 1 );
2217  }
2218  catch ( GEOSException &e )
2219  {
2220  qWarning( "GEOS exception: %s", e.what() );
2221  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
2222  return false;
2223  }
2224 }
2225 
2227 {
2228  if ( !mGeos )
2229  createGeosGeom();
2230  if ( !other->mGeos )
2231  other->createGeosGeom();
2232 
2233  GEOSContextHandle_t ctxt = QgsGeos::getGEOSHandler();
2234  try
2235  {
2236  GEOSGeometry *g1 = GEOSGeom_clone_r( ctxt, mGeos );
2237  GEOSGeometry *g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
2238  GEOSGeometry *geoms[2] = { g1, g2 };
2239  geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
2240  geos::unique_ptr gTmp( GEOSLineMerge_r( ctxt, g.get() ) );
2241 
2242  if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
2243  {
2244  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
2245  return false;
2246  }
2247  invalidateGeos();
2248 
2249  // set up new geometry
2250  mGeos = gTmp.release();
2251  mOwnsGeom = true;
2252 
2253  deleteCoords();
2254  qDeleteAll( mHoles );
2255  mHoles.clear();
2256  extractCoords( mGeos );
2257  return true;
2258  }
2259  catch ( GEOSException &e )
2260  {
2261  qWarning( "GEOS exception: %s", e.what() );
2262  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
2263  return false;
2264  }
2265 }
2266 
2268 {
2269  if ( mLF->alwaysShow() )
2270  {
2271  //if feature is set to always show, bump the priority up by orders of magnitude
2272  //so that other feature's labels are unlikely to be placed over the label for this feature
2273  //(negative numbers due to how pal::extract calculates inactive cost)
2274  return -0.2;
2275  }
2276 
2277  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
2278 }
2279 
2281 {
2282  bool uprightLabel = false;
2283 
2284  switch ( mLF->layer()->upsidedownLabels() )
2285  {
2286  case Layer::Upright:
2287  uprightLabel = true;
2288  break;
2289  case Layer::ShowDefined:
2290  // upright only dynamic labels
2291  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
2292  {
2293  uprightLabel = true;
2294  }
2295  break;
2296  case Layer::ShowAll:
2297  break;
2298  default:
2299  uprightLabel = true;
2300  }
2301  return uprightLabel;
2302 }
2303 
2304 bool FeaturePart::nextCharPosition( double charWidth, double segmentLength, PointSet *path_positions, int &index, double &currentDistanceAlongSegment,
2305  double &characterStartX, double &characterStartY, double &characterEndX, double &characterEndY ) const
2306 {
2307  // Coordinates this character will start at
2308  if ( qgsDoubleNear( segmentLength, 0.0 ) )
2309  {
2310  // Not allowed to place across on 0 length segments or discontinuities
2311  return false;
2312  }
2313 
2314  double segmentStartX = path_positions->x[index - 1];
2315  double segmentStartY = path_positions->y[index - 1];
2316 
2317  double segmentEndX = path_positions->x[index];
2318  double segmentEndY = path_positions->y[index];
2319 
2320  double segmentDx = segmentEndX - segmentStartX;
2321  double segmentDy = segmentEndY - segmentStartY;
2322 
2323  characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
2324  characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
2325 
2326  // Coordinates this character ends at, calculated below
2327  characterEndX = 0;
2328  characterEndY = 0;
2329 
2330  if ( segmentLength - currentDistanceAlongSegment >= charWidth )
2331  {
2332  // if the distance remaining in this segment is enough, we just go further along the segment
2333  currentDistanceAlongSegment += charWidth;
2334  characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
2335  characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
2336  }
2337  else
2338  {
2339  // If there isn't enough distance left on this segment
2340  // then we need to search until we find the line segment that ends further than ci.width away
2341  do
2342  {
2343  segmentStartX = segmentEndX;
2344  segmentStartY = segmentEndY;
2345  index++;
2346  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
2347  {
2348  return false;
2349  }
2350  segmentEndX = path_positions->x[index];
2351  segmentEndY = path_positions->y[index];
2352  }
2353  while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth ); // Distance from start_ to new_
2354 
2355  // Calculate the position to place the end of the character on
2356  GeomFunction::findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
2357 
2358  // Need to calculate distance on the new segment
2359  currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
2360  }
2361  return true;
2362 }
QgsMargins::bottom
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
qgspolygon.h
pal::Layer::maximumPointLabelCandidates
std::size_t maximumPointLabelCandidates() const
Returns the maximum number of point label candidates to generate for features in this layer.
Definition: layer.h:106
QgsLabelFeature::id
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
Definition: qgslabelfeature.h:66
QgsLabelFeature::positionOffset
QgsPointXY positionOffset() const
Applies only to "offset from point" placement strategy.
Definition: qgslabelfeature.h:241
QgsLabelFeature::offsetType
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
Definition: qgslabelfeature.h:255
pal::FeaturePart::mergeWithFeaturePart
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged).
Definition: feature.cpp:2226
pal::LabelPosition::incrementUpsideDownCharCount
int incrementUpsideDownCharCount()
Increases the count of upside down characters for this label position.
Definition: labelposition.h:291
pal::PointSet::length
double length() const
Returns length of line geometry.
Definition: pointset.cpp:964
QgsRectangle::height
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
pal::PointSet::xmax
double xmax
Definition: pointset.h:230
pal::GeomFunction::dist_euc2d
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:66
pal::LabelPosition::getY
double getY(int i=0) const
Returns the down-left y coordinate.
Definition: labelposition.cpp:356
pal::FeaturePart::maximumPointCandidates
std::size_t maximumPointCandidates() const
Returns the maximum number of point candidates to generate for this feature.
Definition: feature.cpp:162
pal::Layer::ShowAll
@ ShowAll
Definition: layer.h:75
QgsLabelFeature::priority
double priority() const
Returns the feature's labeling priority.
Definition: qgslabelfeature.h:144
QgsLabelFeature::fixedAngle
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true)
Definition: qgslabelfeature.h:203
QgsPointXY::y
double y
Definition: qgspointxy.h:48
QgsPalLayerSettings::PerimeterCurved
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
Definition: qgspallabeling.h:230
pal::Pal::maximumPolygonCandidatesPerMapUnitSquared
double maximumPolygonCandidatesPerMapUnitSquared() const
Returns the maximum number of polygon label candidate positions per map unit squared.
Definition: pal.h:185
QgsMargins::top
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
pal::LabelInfo
Optional additional info about label (for curved labels)
Definition: feature.h:55
pal::LabelPosition::Quadrant
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:66
QgsPalLayerSettings::AroundPoint
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
Definition: qgspallabeling.h:223
pal::Layer::isCurved
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:182
pal::FeaturePart::createCandidatesAroundPoint
std::size_t createCandidatesAroundPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate candidates for point feature, located around a specified point.
Definition: feature.cpp:568
QgsPolygon
Polygon geometry type.
Definition: qgspolygon.h:34
QgsLineString::length
double length() const override SIP_HOLDGIL
Returns the planar, 2-dimensional length of the geometry.
Definition: qgslinestring.cpp:636
pal::Layer::connectedFeatureId
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:343
QgsPalLayerSettings::FromSymbolBounds
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
Definition: qgspallabeling.h:261
pal::LabelInfo::CharacterInfo
Definition: feature.h:58
pal::FeaturePart::getLabelWidth
double getLabelWidth(double angle=0.0) const
Returns the width of the label, optionally taking an angle into account.
Definition: feature.h:291
pal::LabelInfo::max_char_angle_inside
double max_char_angle_inside
Definition: feature.h:78
labelposition.h
pal::FeaturePart::createCandidatesForPolygon
std::size_t createCandidatesForPolygon(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate candidates for polygon features.
Definition: feature.cpp:1623
QgsCurvePolygon::exteriorRing
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
Definition: qgscurvepolygon.h:87
QgsPointXY::x
Q_GADGET double x
Definition: qgspointxy.h:47
pal::Layer::centroidInside
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:294
pal::LabelPosition
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
MapOrientation
MapOrientation
Signifies that the AboveLine and BelowLine flags should respect the map's orientation rather than the...
Definition: qgspallabeling.h:280
QgsLabelFeature::symbolSize
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
Definition: qgslabelfeature.h:136
pal::Layer::arrangement
QgsPalLayerSettings::Placement arrangement() const
Returns the layer's arrangement policy.
Definition: layer.h:177
pal::PointSet::compute_chull_bbox
CHullBox compute_chull_bbox()
Computes a con???? hull.
Definition: pointset.cpp:641
AboveLine
AboveLine
Labels can be placed above a line feature.
Definition: qgspallabeling.h:274
pal::LabelPosition::QuadrantOver
@ QuadrantOver
Definition: labelposition.h:71
QgsLabelFeature::visualMargin
const QgsMargins & visualMargin() const
Returns the visual margin for the label feature.
Definition: qgslabelfeature.h:119
pal::LabelPosition::QuadrantAboveLeft
@ QuadrantAboveLeft
Definition: labelposition.h:67
pal::FeaturePart::createCandidateCenteredOverPoint
std::size_t createCandidateCenteredOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate one candidate centered over the specified point.
Definition: feature.cpp:289
QgsPalLayerSettings::MiddleRight
@ MiddleRight
Label on right of point.
Definition: qgspallabeling.h:244
QgsPalLayerSettings::TopRight
@ TopRight
Label on top-right of point.
Definition: qgspallabeling.h:242
qgis.h
pal::PointSet::mOwnsGeom
bool mOwnsGeom
Definition: pointset.h:206
pal::FeaturePart::createCurvedCandidatesAlongLine
std::size_t createCurvedCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal)
Generate curved candidates for line features.
Definition: feature.cpp:1401
layer.h
pal::FeaturePart::createCandidatePointOnSurface
std::unique_ptr< LabelPosition > createCandidatePointOnSurface(PointSet *mapShape)
Creates a single candidate using the "point on sruface" algorithm.
Definition: feature.cpp:397
pal::FeaturePart::curvedPlacementAtOffset
std::unique_ptr< LabelPosition > curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int &orientation, double distance, bool &reversed, bool &flip, bool applyAngleConstraints)
Returns the label position for a curved label at a specific offset along a path.
Definition: feature.cpp:1226
QgsLineString
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
pal::Pal::maximumLineCandidatesPerMapUnit
double maximumLineCandidatesPerMapUnit() const
Returns the maximum number of line label candidate positions per map unit.
Definition: pal.h:171
QgsLabelFeature::permissibleZonePrepared
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label's permissibleZone().
Definition: qgslabelfeature.h:98
QgsPalLayerSettings::Line
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition: qgspallabeling.h:225
pal::LabelPosition::nextPart
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
Definition: labelposition.h:277
QgsLabelFeature::arrangementFlags
QgsLabeling::LinePlacementFlags arrangementFlags() const
Returns the feature's arrangement flags.
Definition: qgslabelfeature.h:312
QgsPalLayerSettings::TopSlightlyRight
@ TopSlightlyRight
Label on top of point, slightly right of center.
Definition: qgspallabeling.h:241
pal::FeaturePart::createHorizontalCandidatesAlongLine
std::size_t createHorizontalCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate horizontal candidates for line feature.
Definition: feature.cpp:746
pal::FeaturePart::createCandidatesAlongLineNearMidpoint
std::size_t createCandidatesAlongLineNearMidpoint(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, double initialCost=0.0, Pal *pal=nullptr)
Generate candidates for line feature, by trying to place candidates as close as possible to the line'...
Definition: feature.cpp:1051
QgsPalLayerSettings::OverPoint
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition: qgspallabeling.h:224
pal::LabelInfo::char_num
int char_num
Definition: feature.h:81
pal::PointSet::deleteCoords
void deleteCoords()
Definition: pointset.cpp:208
pal::FeaturePart::hasFixedRotation
bool hasFixedRotation() const
Returns true if the feature's label has a fixed rotation.
Definition: feature.h:306
QgsGeometryUtils::normalizedAngle
static double normalizedAngle(double angle) SIP_HOLDGIL
Ensures that an angle is in the range 0 <= angle < 2 pi.
Definition: qgsgeometryutils.cpp:1292
pal
Definition: qgsdiagramrenderer.h:49
QgsPalLayerSettings::TopMiddle
@ TopMiddle
Label directly above point.
Definition: qgspallabeling.h:240
QgsLabelFeature::hasFixedQuadrant
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Definition: qgslabelfeature.h:213
pal::PointSet::containsPoint
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:246
createCandidateAtOrderedPositionOverPoint
void createCandidateAtOrderedPositionOverPoint(double &labelX, double &labelY, LabelPosition::Quadrant &quadrant, double x, double y, double labelWidth, double labelHeight, QgsPalLayerSettings::PredefinedPointPosition position, double distanceToLabel, const QgsMargins &visualMargin, double symbolWidthOffset, double symbolHeightOffset)
Definition: feature.cpp:429
pal::FeaturePart::layer
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:152
pal::PointSet::holeOf
PointSet * holeOf
Definition: pointset.h:213
pal::GeomFunction::containsCandidate
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.
Definition: geomfunction.cpp:321
pal::PointSet::isClosed
bool isClosed() const
Returns true if pointset is closed.
Definition: pointset.cpp:1017
pal::FeaturePart::addSizePenalty
void addSizePenalty(std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4])
Increases the cost of the label candidates for this feature, based on the size of the feature.
Definition: feature.cpp:2168
geos::unique_ptr
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
pal::LabelPosition::QuadrantBelowRight
@ QuadrantBelowRight
Definition: labelposition.h:75
pal::PointSet::geos
const GEOSGeometry * geos() const
Returns the point set's GEOS geometry.
Definition: pointset.cpp:956
pal::PointSet::ymin
double ymin
Definition: pointset.h:231
QgsPalLayerSettings::BottomMiddle
@ BottomMiddle
Label directly below point.
Definition: qgspallabeling.h:247
pal::GeomFunction::reorderPolygon
static int reorderPolygon(int nbPoints, std::vector< double > &x, std::vector< double > &y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside.
Definition: geomfunction.cpp:267
QgsPalLayerSettings::OrderedPositionsAroundPoint
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
Definition: qgspallabeling.h:229
pal::LabelPosition::QuadrantBelowLeft
@ QuadrantBelowLeft
Definition: labelposition.h:73
QgsMargins::left
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
feature.h
QgsLabelFeature::hasFixedPosition
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
Definition: qgslabelfeature.h:173
pal::Layer::name
QString name() const
Returns the layer's name.
Definition: layer.h:171
pal::FeaturePart::isConnected
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:2209
QgsLabelFeature::hasFixedAngle
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
Definition: qgslabelfeature.h:199
QgsLabelFeature::lineAnchorPercent
double lineAnchorPercent() const
Returns the percent along the line at which labels should be placed, for line labels only.
Definition: qgslabelfeature.h:445
pal::FeaturePart::createCandidatesAlongLine
std::size_t createCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal)
Generate candidates for line feature.
Definition: feature.cpp:717
costcalculator.h
pal::PointSet::FeaturePart
friend class FeaturePart
Definition: pointset.h:73
QgsLabelFeature::overrunDistance
double overrunDistance() const
Returns the permissible distance (in map units) which labels are allowed to overrun the start or end ...
Definition: qgslabelfeature.cpp:83
QgsLabelLineSettings::AnchorType::Strict
@ Strict
Line anchor is a strict placement, and other placements are not permitted.
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsPalLayerSettings::TopSlightlyLeft
@ TopSlightlyLeft
Label on top of point, slightly left of center.
Definition: qgspallabeling.h:239
QgsPalLayerSettings::BottomSlightlyLeft
@ BottomSlightlyLeft
Label below point, slightly left of center.
Definition: qgspallabeling.h:246
pal::Layer::priority
double priority() const
Returns the layer's priority, between 0 and 1.
Definition: layer.h:252
QgsLabelFeature::lineAnchorType
QgsLabelLineSettings::AnchorType lineAnchorType() const
Returns the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
Definition: qgslabelfeature.h:469
pal::FeaturePart::getLabelDistance
double getLabelDistance() const
Returns the distance from the anchor point to the label.
Definition: feature.h:303
pal::PointSet::x
std::vector< double > x
Definition: pointset.h:201
pal::PointSet::type
int type
Definition: pointset.h:211
geomfunction.h
pointset.h
QgsPalLayerSettings::MiddleLeft
@ MiddleLeft
Label on left of point.
Definition: qgspallabeling.h:243
QgsMargins
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:38
QgsPalLayerSettings::PredefinedPointPosition
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode.
Definition: qgspallabeling.h:237
pal::FeaturePart
Main class to handle feature.
Definition: feature.h:96
QgsLabelFeature::polygonPlacementFlags
QgsLabeling::PolygonPlacementFlags polygonPlacementFlags() const
Returns the polygon placement flags, which dictate how polygon labels can be placed.
Definition: qgslabelfeature.h:327
QgsMargins::right
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
pal::FeaturePart::hasSameLabelFeatureAs
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part.
Definition: feature.cpp:211
QgsLabelFeature::setAnchorPosition
void setAnchorPosition(const QgsPointXY &anchorPosition)
In case of quadrand or aligned positioning, this is set to the anchor point.
Definition: qgslabelfeature.cpp:113
pal::LabelPosition::setNextPart
void setNextPart(std::unique_ptr< LabelPosition > next)
Sets the next part of this label position (i.e.
Definition: labelposition.h:284
pal::PointSet::clone
std::unique_ptr< PointSet > clone() const
Returns a copy of the point set.
Definition: pointset.cpp:241
pal::Layer::maximumLineLabelCandidates
std::size_t maximumLineLabelCandidates() const
Returns the maximum number of line label candidates to generate for features in this layer.
Definition: layer.h:127
pal::FeaturePart::nextCharPosition
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:2304
QgsLabelFeature::layer
pal::Layer * layer() const
Gets PAL layer of the label feature. Should be only used internally in PAL.
Definition: qgslabelfeature.h:353
QgsLabelLineSettings::AnchorType::HintOnly
@ HintOnly
Line anchor is a hint for preferred placement only, but other placements close to the hint are permit...
pal::LabelPosition::QuadrantAboveRight
@ QuadrantAboveRight
Definition: labelposition.h:69
pal::PointSet::invalidateGeos
void invalidateGeos()
Definition: pointset.cpp:179
QgsPalLayerSettings::BottomSlightlyRight
@ BottomSlightlyRight
Label below point, slightly right of center.
Definition: qgspallabeling.h:248
QgsGeos::getGEOSHandler
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2888
QgsPalLayerSettings::Horizontal
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
Definition: qgspallabeling.h:227
qgsgeometryutils.h
pal::FeaturePart::createCandidates
std::vector< std::unique_ptr< LabelPosition > > createCandidates(Pal *pal)
Generates a list of candidate positions for labels for this feature.
Definition: feature.cpp:2063
pal::CHullBox
Definition: pointset.h:56
QgsLabelFeature::alwaysShow
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
Definition: qgslabelfeature.h:304
pal::PointSet::createGeosGeom
void createGeosGeom() const
Definition: pointset.cpp:117
OnLine
OnLine
Labels can be placed directly over a line feature.
Definition: qgspallabeling.h:273
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:44
pal::FeaturePart::mLF
QgsLabelFeature * mLF
Definition: feature.h:376
pal::LabelInfo::max_char_angle_outside
double max_char_angle_outside
Definition: feature.h:79
qgslabeling.h
pal::PointSet::mGeos
GEOSGeometry * mGeos
Definition: pointset.h:205
pal::PointSet::y
std::vector< double > y
Definition: pointset.h:202
pal::LabelInfo::char_info
CharacterInfo * char_info
Definition: feature.h:82
QgsPalLayerSettings::TopLeft
@ TopLeft
Label on top-left of point.
Definition: qgspallabeling.h:238
pal::PointSet
Definition: pointset.h:72
pal::PointSet::parent
PointSet * parent
Definition: pointset.h:214
pal::FeaturePart::maximumPolygonCandidates
std::size_t maximumPolygonCandidates() const
Returns the maximum number of polygon candidates to generate for this feature.
Definition: feature.cpp:189
pal::LabelPosition::QuadrantLeft
@ QuadrantLeft
Definition: labelposition.h:70
pal::FeaturePart::calculatePriority
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:2267
qgsgeometry.h
pal::LabelPosition::getHeight
double getHeight() const
Definition: labelposition.h:261
pal::PointSet::xmin
double xmin
Definition: pointset.h:229
pal::PointSet::preparedGeom
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:167
QgsLabelFeature::quadOffset
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
Definition: qgslabelfeature.h:229
QgsRectangle::width
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
pal::Layer::Upright
@ Upright
Definition: layer.h:73
QgsLabelFeature::overrunSmoothDistance
double overrunSmoothDistance() const
Returns the distance (in map units) with which the ends of linear features are averaged over when cal...
Definition: qgslabelfeature.cpp:93
QgsPalLayerSettings::OutsidePolygons
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only....
Definition: qgspallabeling.h:231
pal::LabelPosition::getX
double getX(int i=0) const
Returns the down-left x coordinate.
Definition: labelposition.cpp:351
pal::FeaturePart::hasFixedPosition
bool hasFixedPosition() const
Returns true if the feature's label has a fixed position.
Definition: feature.h:312
QgsLabelFeature::distLabel
double distLabel() const
Applies to "around point" placement strategy or linestring features.
Definition: qgslabelfeature.h:269
QgsMessageLog::logMessage
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).
Definition: qgsmessagelog.cpp:27
pal::FeaturePart::totalRepeats
int totalRepeats() const
Returns the total number of repeating labels associated with this label.
Definition: feature.cpp:279
pal::PointSet::getCentroid
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:849
pal::Pal
Main Pal labeling class.
Definition: pal.h:80
pal::FeaturePart::maximumLineCandidates
std::size_t maximumLineCandidates() const
Returns the maximum number of line candidates to generate for this feature.
Definition: feature.cpp:167
QgsLabelFeature::fixedPosition
QgsPointXY fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true)
Definition: qgslabelfeature.h:177
pal::FeaturePart::showUprightLabels
bool showUprightLabels() const
Returns true if feature's label must be displayed upright.
Definition: feature.cpp:2280
pal::Layer
A set of features which influence the labeling process.
Definition: layer.h:62
pal::FeaturePart::getLabelHeight
double getLabelHeight(double angle=0.0) const
Returns the height of the label, optionally taking an angle into account.
Definition: feature.h:297
geos::prepared_unique_ptr
std::unique_ptr< const GEOSPreparedGeometry, GeosDeleter > prepared_unique_ptr
Scoped GEOS prepared geometry pointer.
Definition: qgsgeos.h:84
QgsPalLayerSettings::BottomRight
@ BottomRight
Label on bottom right of point.
Definition: qgspallabeling.h:249
pal::LabelPosition::QuadrantRight
@ QuadrantRight
Definition: labelposition.h:72
QgsGeometry::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Definition: qgsgeometry.cpp:996
pal::FeaturePart::createCandidatesAlongLineNearStraightSegments
std::size_t createCandidatesAlongLineNearStraightSegments(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate candidates for line feature, by trying to place candidates towards the middle of the longest...
Definition: feature.cpp:817
pal::GeomFunction::findLineCircleIntersection
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
Definition: geomfunction.cpp:395
pal::Layer::upsidedownLabels
UpsideDownLabels upsidedownLabels() const
Returns how upside down labels are handled within the layer.
Definition: layer.h:278
pal.h
QgsLabelFeature
The QgsLabelFeature class describes a feature that should be used within the labeling engine.
Definition: qgslabelfeature.h:57
pal::LabelInfo::label_height
double label_height
Definition: feature.h:80
pal::LabelPosition::getAlpha
double getAlpha() const
Returns the angle to rotate text (in rad).
Definition: labelposition.cpp:361
pal::FeaturePart::fixedAngle
double fixedAngle() const
Returns the fixed angle for the feature's label.
Definition: feature.h:309
pal::FeaturePart::featureId
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:157
pal::FeaturePart::mHoles
QList< FeaturePart * > mHoles
Definition: feature.h:377
QgsLabelFeature::curvedLabelInfo
pal::LabelInfo * curvedLabelInfo() const
Gets additional info required for curved label placement. Returns nullptr if not set.
Definition: qgslabelfeature.h:348
QgsGeos::fromGeos
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition: qgsgeos.cpp:1087
pal::FeaturePart::feature
QgsLabelFeature * feature()
Returns the parent feature.
Definition: feature.h:117
pal::LabelPosition::QuadrantBelow
@ QuadrantBelow
Definition: labelposition.h:74
pal::LabelPosition::QuadrantAbove
@ QuadrantAbove
Definition: labelposition.h:68
pal::Layer::maximumPolygonLabelCandidates
std::size_t maximumPolygonLabelCandidates() const
Returns the maximum number of polygon label candidates to generate for features in this layer.
Definition: layer.h:148
QgsPalLayerSettings::Free
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
Definition: qgspallabeling.h:228
MathUtils::angle
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
QgsLabelFeature::permissibleZone
QgsGeometry permissibleZone() const
Returns the label's permissible zone geometry.
Definition: qgslabelfeature.h:90
util.h
pal::FeaturePart::setTotalRepeats
void setTotalRepeats(int repeats)
Returns the total number of repeating labels associated with this label.
Definition: feature.cpp:284
pal::FeaturePart::~FeaturePart
~FeaturePart() override
Delete the feature.
Definition: feature.cpp:79
pal::FeaturePart::extractCoords
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:87
pal::PointSet::nbPoints
int nbPoints
Definition: pointset.h:200
pal::PointSet::splitPolygons
static void splitPolygons(QLinkedList< PointSet * > &inputShapes, QLinkedList< PointSet * > &outputShapes, double xrm, double yrm)
Split a concave shape into several convex shapes.
Definition: pointset.cpp:277
BelowLine
BelowLine
Labels can be placed below a line feature.
Definition: qgspallabeling.h:277
pal::Layer::mPal
Pal * mPal
Definition: layer.h:332
pal::PointSet::area
double area() const
Returns area of polygon geometry.
Definition: pointset.cpp:990
QgsLabelFeature::predefinedPositionOrder
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
Definition: qgslabelfeature.h:282
pal::FeaturePart::createCandidatesAtOrderedPositionsOverPoint
std::size_t createCandidatesAtOrderedPositionsOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generates candidates following a prioritized list of predefined positions around a point.
Definition: feature.cpp:529
pal::Layer::ShowDefined
@ ShowDefined
Definition: layer.h:74
qgsgeos.h
pal::FeaturePart::createCandidatesOverPoint
std::size_t createCandidatesOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:318
pal::FeaturePart::createCandidatesOutsidePolygon
std::size_t createCandidatesOutsidePolygon(std::vector< std::unique_ptr< LabelPosition > > &lPos, Pal *pal)
Generate candidates outside of polygon features.
Definition: feature.cpp:1860
pal::PointSet::getPointByDistance
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:917
QgsPalLayerSettings::BottomLeft
@ BottomLeft
Label on bottom-left of point.
Definition: qgspallabeling.h:245
QgsLineString::visitPointsByRegularDistance
void visitPointsByRegularDistance(double distance, const std::function< bool(double x, double y, double z, double m, double startSegmentX, double startSegmentY, double startSegmentZ, double startSegmentM, double endSegmentX, double endSegmentY, double endSegmentZ, double endSegmentM) > &visitPoint) const
Visits regular points along the linestring, spaced by distance.
Definition: qgslinestring.cpp:946
QgsFeatureId
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
qgsmessagelog.h
pal::LabelPosition::getWidth
double getWidth() const
Definition: labelposition.h:260
pal::LabelInfo::CharacterInfo::width
double width
Definition: feature.h:59
pal::PointSet::ymax
double ymax
Definition: pointset.h:232