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