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