QGIS API Documentation  2.99.0-Master (c558d51)
feature.cpp
Go to the documentation of this file.
1 /*
2  * libpal - Automated Placement of Labels Library
3  *
4  * Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5  * University of Applied Sciences, Western Switzerland
6  * http://www.hes-so.ch
7  *
8  * Contact:
9  * maxence.laurent <at> heig-vd <dot> ch
10  * or
11  * eric.taillard <at> heig-vd <dot> ch
12  *
13  * This file is part of libpal.
14  *
15  * libpal is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation, either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * libpal is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with libpal. If not, see <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 #include "qgsgeometry.h"
31 #include "pal.h"
32 #include "layer.h"
33 #include "feature.h"
34 #include "geomfunction.h"
35 #include "labelposition.h"
36 #include "pointset.h"
37 #include "util.h"
38 #include "qgis.h"
39 #include "qgsgeos.h"
40 #include "qgsmessagelog.h"
41 #include "costcalculator.h"
42 #include "qgsgeometryutils.h"
43 #include <QLinkedList>
44 #include <cmath>
45 #include <cfloat>
46 
47 #ifndef M_PI
48 #define M_PI 3.14159265358979323846
49 #endif
50 
51 using namespace pal;
52 
53 FeaturePart::FeaturePart( QgsLabelFeature* feat, const GEOSGeometry* geom )
54  : mLF( feat )
55 {
56  // we'll remove const, but we won't modify that geometry
57  mGeos = const_cast<GEOSGeometry*>( geom );
58  mOwnsGeom = false; // geometry is owned by Feature class
59 
60  extractCoords( geom );
61 
62  holeOf = nullptr;
63  for ( int i = 0; i < mHoles.count(); i++ )
64  {
65  mHoles.at( i )->holeOf = this;
66  }
67 
68 }
69 
71  : PointSet( other )
72  , mLF( other.mLF )
73 {
74  Q_FOREACH ( const FeaturePart* hole, other.mHoles )
75  {
76  mHoles << new FeaturePart( *hole );
77  mHoles.last()->holeOf = this;
78  }
79 }
80 
82 {
83  // X and Y are deleted in PointSet
84 
85  qDeleteAll( mHoles );
86  mHoles.clear();
87 }
88 
89 void FeaturePart::extractCoords( const GEOSGeometry* geom )
90 {
91  const GEOSCoordSequence *coordSeq;
92  GEOSContextHandle_t geosctxt = geosContext();
93 
94  type = GEOSGeomTypeId_r( geosctxt, geom );
95 
96  if ( type == GEOS_POLYGON )
97  {
98  if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
99  {
100  int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
101 
102  for ( int i = 0; i < numHoles; ++i )
103  {
104  const GEOSGeometry* interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
105  FeaturePart* hole = new FeaturePart( mLF, interior );
106  hole->holeOf = nullptr;
107  // possibly not needed. it's not done for the exterior ring, so I'm not sure
108  // why it's just done here...
109  GeomFunction::reorderPolygon( hole->nbPoints, hole->x, hole->y );
110 
111  mHoles << hole;
112  }
113  }
114 
115  // use exterior ring for the extraction of coordinates that follows
116  geom = GEOSGetExteriorRing_r( geosctxt, geom );
117  }
118  else
119  {
120  qDeleteAll( mHoles );
121  mHoles.clear();
122  }
123 
124  // find out number of points
125  nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
126  coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
127 
128  // initialize bounding box
129  xmin = ymin = DBL_MAX;
130  xmax = ymax = -DBL_MAX;
131 
132  // initialize coordinate arrays
133  deleteCoords();
134  x = new double[nbPoints];
135  y = new double[nbPoints];
136 
137  for ( int i = 0; i < nbPoints; ++i )
138  {
139  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] );
140  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] );
141 
142  xmax = x[i] > xmax ? x[i] : xmax;
143  xmin = x[i] < xmin ? x[i] : xmin;
144 
145  ymax = y[i] > ymax ? y[i] : ymax;
146  ymin = y[i] < ymin ? y[i] : ymin;
147  }
148 }
149 
151 {
152  return mLF->layer();
153 }
154 
156 {
157  return mLF->id();
158 }
159 
161 {
162  if ( !part )
163  return false;
164 
165  if ( mLF->layer()->name() != part->layer()->name() )
166  return false;
167 
168  if ( mLF->id() == part->featureId() )
169  return true;
170 
171  // any part of joined features are also treated as having the same label feature
172  int connectedFeatureId = mLF->layer()->connectedFeatureId( mLF->id() );
173  if ( connectedFeatureId >= 0 && connectedFeatureId == mLF->layer()->connectedFeatureId( part->featureId() ) )
174  return true;
175 
176  return false;
177 }
178 
179 LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
180 {
181  QPointF quadOffset = mLF->quadOffset();
182  qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
183 
184  if ( quadOffsetX < 0 )
185  {
186  if ( quadOffsetY < 0 )
187  {
189  }
190  else if ( quadOffsetY > 0 )
191  {
193  }
194  else
195  {
197  }
198  }
199  else if ( quadOffsetX > 0 )
200  {
201  if ( quadOffsetY < 0 )
202  {
204  }
205  else if ( quadOffsetY > 0 )
206  {
208  }
209  else
210  {
212  }
213  }
214  else
215  {
216  if ( quadOffsetY < 0 )
217  {
219  }
220  else if ( quadOffsetY > 0 )
221  {
223  }
224  else
225  {
227  }
228  }
229 }
230 
231 int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosition*>& lPos, double angle )
232 {
233  int nbp = 1;
234 
235  // get from feature
236  double labelW = getLabelWidth();
237  double labelH = getLabelHeight();
238 
239  double cost = 0.0001;
240  int id = 0;
241 
242  double xdiff = -labelW / 2.0;
243  double ydiff = -labelH / 2.0;
244 
245  if ( !qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
246  {
247  xdiff += labelW / 2.0 * mLF->quadOffset().x();
248  }
249  if ( !qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
250  {
251  ydiff += labelH / 2.0 * mLF->quadOffset().y();
252  }
253 
254  if ( ! mLF->hasFixedPosition() )
255  {
256  if ( !qgsDoubleNear( angle, 0.0 ) )
257  {
258  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
259  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
260  xdiff = xd;
261  ydiff = yd;
262  }
263  }
264 
266  {
267  //if in "around point" placement mode, then we use the label distance to determine
268  //the label's offset
269  if ( qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
270  {
271  ydiff += mLF->quadOffset().y() * mLF->distLabel();
272  }
273  else if ( qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
274  {
275  xdiff += mLF->quadOffset().x() * mLF->distLabel();
276  }
277  else
278  {
279  xdiff += mLF->quadOffset().x() * M_SQRT1_2 * mLF->distLabel();
280  ydiff += mLF->quadOffset().y() * M_SQRT1_2 * mLF->distLabel();
281  }
282  }
283  else
284  {
285  if ( !qgsDoubleNear( mLF->positionOffset().x(), 0.0 ) )
286  {
287  xdiff += mLF->positionOffset().x();
288  }
289  if ( !qgsDoubleNear( mLF->positionOffset().y(), 0.0 ) )
290  {
291  ydiff += mLF->positionOffset().y();
292  }
293  }
294 
295  double lx = x + xdiff;
296  double ly = y + ydiff;
297 
298  if ( mLF->permissibleZonePrepared() )
299  {
300  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
301  {
302  return 0;
303  }
304  }
305 
306  lPos << new LabelPosition( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() );
307  return nbp;
308 }
309 
310 int FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y, QList<LabelPosition*>& lPos, double angle )
311 {
312  QVector< QgsPalLayerSettings::PredefinedPointPosition > positions = mLF->predefinedPositionOrder();
313  double labelWidth = getLabelWidth();
314  double labelHeight = getLabelHeight();
315  double distanceToLabel = getLabelDistance();
316  const QgsLabelFeature::VisualMargin& visualMargin = mLF->visualMargin();
317 
318  double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
319  double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );
320 
321  double cost = 0.0001;
322  int i = 0;
323  Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
324  {
325  double alpha = 0.0;
326  double deltaX = 0;
327  double deltaY = 0;
329  switch ( position )
330  {
333  alpha = 3 * M_PI_4;
334  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
335  deltaY = -visualMargin.bottom + symbolHeightOffset;
336  break;
337 
339  quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
340  alpha = M_PI_2;
341  deltaX = -labelWidth / 4.0 - visualMargin.left;
342  deltaY = -visualMargin.bottom + symbolHeightOffset;
343  break;
344 
346  quadrant = LabelPosition::QuadrantAbove;
347  alpha = M_PI_2;
348  deltaX = -labelWidth / 2.0;
349  deltaY = -visualMargin.bottom + symbolHeightOffset;
350  break;
351 
353  quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
354  alpha = M_PI_2;
355  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
356  deltaY = -visualMargin.bottom + symbolHeightOffset;
357  break;
358 
361  alpha = M_PI_4;
362  deltaX = - visualMargin.left + symbolWidthOffset;
363  deltaY = -visualMargin.bottom + symbolHeightOffset;
364  break;
365 
367  quadrant = LabelPosition::QuadrantLeft;
368  alpha = M_PI;
369  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
370  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
371  break;
372 
374  quadrant = LabelPosition::QuadrantRight;
375  alpha = 0.0;
376  deltaX = -visualMargin.left + symbolWidthOffset;
377  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
378  break;
379 
382  alpha = 5 * M_PI_4;
383  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
384  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
385  break;
386 
388  quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
389  alpha = 3 * M_PI_2;
390  deltaX = -labelWidth / 4.0 - visualMargin.left;
391  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
392  break;
393 
395  quadrant = LabelPosition::QuadrantBelow;
396  alpha = 3 * M_PI_2;
397  deltaX = -labelWidth / 2.0;
398  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
399  break;
400 
402  quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
403  alpha = 3 * M_PI_2;
404  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
405  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
406  break;
407 
410  alpha = 7 * M_PI_4;
411  deltaX = -visualMargin.left + symbolWidthOffset;
412  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
413  break;
414  }
415 
416  //have bearing, distance - calculate reference point
417  double referenceX = cos( alpha ) * distanceToLabel + x;
418  double referenceY = sin( alpha ) * distanceToLabel + y;
419 
420  double labelX = referenceX + deltaX;
421  double labelY = referenceY + deltaY;
422 
423  if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
424  {
425  lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
426  //TODO - tweak
427  cost += 0.001;
428  }
429 
430  ++i;
431  }
432 
433  return lPos.count();
434 }
435 
436 int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPosition* >& lPos, double angle )
437 {
438  double labelWidth = getLabelWidth();
439  double labelHeight = getLabelHeight();
440  double distanceToLabel = getLabelDistance();
441 
442  int numberCandidates = mLF->layer()->pal->point_p;
443 
444  int icost = 0;
445  int inc = 2;
446 
447  double candidateAngleIncrement = 2 * M_PI / numberCandidates; /* angle bw 2 pos */
448 
449  /* various angles */
450  double a90 = M_PI / 2;
451  double a180 = M_PI;
452  double a270 = a180 + a90;
453  double a360 = 2 * M_PI;
454 
455  double gamma1, gamma2;
456 
457  if ( distanceToLabel > 0 )
458  {
459  gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
460  gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
461  }
462  else
463  {
464  gamma1 = gamma2 = a90 / 3.0;
465  }
466 
467  if ( gamma1 > a90 / 3.0 )
468  gamma1 = a90 / 3.0;
469 
470  if ( gamma2 > a90 / 3.0 )
471  gamma2 = a90 / 3.0;
472 
473  QList< LabelPosition* > candidates;
474 
475  int i;
476  double angleToCandidate;
477  for ( i = 0, angleToCandidate = M_PI / 4; i < numberCandidates; i++, angleToCandidate += candidateAngleIncrement )
478  {
479  double labelX = x;
480  double labelY = y;
481 
482  if ( angleToCandidate > a360 )
483  angleToCandidate -= a360;
484 
486 
487  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
488  {
489  labelX += distanceToLabel;
490  double iota = ( angleToCandidate + gamma1 );
491  if ( iota > a360 - gamma1 )
492  iota -= a360;
493 
494  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
495  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
496 
497  quadrant = LabelPosition::QuadrantRight;
498  }
499  else if ( angleToCandidate < a90 - gamma2 ) // top-right
500  {
501  labelX += distanceToLabel * cos( angleToCandidate );
502  labelY += distanceToLabel * sin( angleToCandidate );
504  }
505  else if ( angleToCandidate < a90 + gamma2 ) // top
506  {
507  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
508  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
509  labelY += distanceToLabel;
510  quadrant = LabelPosition::QuadrantAbove;
511  }
512  else if ( angleToCandidate < a180 - gamma1 ) // top left
513  {
514  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
515  labelY += distanceToLabel * sin( angleToCandidate );
517  }
518  else if ( angleToCandidate < a180 + gamma1 ) // left
519  {
520  labelX += -distanceToLabel - labelWidth;
521  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
522  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
523  quadrant = LabelPosition::QuadrantLeft;
524  }
525  else if ( angleToCandidate < a270 - gamma2 ) // down - left
526  {
527  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
528  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
530  }
531  else if ( angleToCandidate < a270 + gamma2 ) // down
532  {
533  labelY += -distanceToLabel - labelHeight;
534  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
535  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
536  quadrant = LabelPosition::QuadrantBelow;
537  }
538  else if ( angleToCandidate < a360 ) // down - right
539  {
540  labelX += distanceToLabel * cos( angleToCandidate );
541  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
543  }
544 
545  double cost;
546 
547  if ( numberCandidates == 1 )
548  cost = 0.0001;
549  else
550  cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
551 
552 
553  if ( mLF->permissibleZonePrepared() )
554  {
555  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
556  {
557  continue;
558  }
559  }
560 
561  candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
562 
563  icost += inc;
564 
565  if ( icost == numberCandidates )
566  {
567  icost = numberCandidates - 1;
568  inc = -2;
569  }
570  else if ( icost > numberCandidates )
571  {
572  icost = numberCandidates - 2;
573  inc = -2;
574  }
575 
576  }
577 
578  if ( !candidates.isEmpty() )
579  {
580  for ( int i = 0; i < candidates.count(); ++i )
581  {
582  lPos << candidates.at( i );
583  }
584  }
585 
586  return candidates.count();
587 }
588 
589 int FeaturePart::createCandidatesAlongLine( QList< LabelPosition* >& lPos, PointSet *mapShape )
590 {
591  //prefer to label along straightish segments:
592  int candidates = createCandidatesAlongLineNearStraightSegments( lPos, mapShape );
593 
594  if ( candidates < mLF->layer()->pal->line_p )
595  {
596  // but not enough candidates yet, so fallback to labeling near whole line's midpoint
597  candidates = createCandidatesAlongLineNearMidpoint( lPos, mapShape, candidates > 0 ? 0.01 : 0.0 );
598  }
599  return candidates;
600 }
601 
602 int FeaturePart::createCandidatesAlongLineNearStraightSegments( QList<LabelPosition*>& lPos, PointSet* mapShape )
603 {
604  double labelWidth = getLabelWidth();
605  double labelHeight = getLabelHeight();
606  double distanceLineToLabel = getLabelDistance();
607  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
608  if ( flags == 0 )
609  flags = FLAG_ON_LINE; // default flag
610 
611  // 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
612  QVector< int > extremeAngleNodes;
613  PointSet * line = mapShape;
614  int numberNodes = line->nbPoints;
615  double *x = line->x;
616  double *y = line->y;
617 
618  // closed line? if so, we need to handle the final node angle
619  bool closedLine = qgsDoubleNear( x[0], x[ numberNodes - 1] ) && qgsDoubleNear( y[0], y[numberNodes - 1 ] );
620  for ( int i = 1; i <= numberNodes - ( closedLine ? 1 : 2 ); ++i )
621  {
622  double x1 = x[i-1];
623  double x2 = x[i];
624  double x3 = x[ i == numberNodes - 1 ? 1 : i+1]; // wraparound for closed linestrings
625  double y1 = y[i-1];
626  double y2 = y[i];
627  double y3 = y[ i == numberNodes - 1 ? 1 : i+1]; // wraparound for closed linestrings
628  if ( qgsDoubleNear( y2, y3 ) && qgsDoubleNear( x2, x3 ) )
629  continue;
630  if ( qgsDoubleNear( y1, y2 ) && qgsDoubleNear( x1, x2 ) )
631  continue;
632  double vertexAngle = M_PI - ( atan2( y3 - y2, x3 - x2 ) - atan2( y2 - y1, x2 - x1 ) );
633  vertexAngle = QgsGeometryUtils::normalizedAngle( vertexAngle );
634 
635  // extreme angles form more than 45 degree angle at a node - these are the ones we don't want labels to cross
636  if ( vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0 )
637  extremeAngleNodes << i;
638  }
639  extremeAngleNodes << numberNodes - 1;
640 
641  if ( extremeAngleNodes.isEmpty() )
642  {
643  // no extreme angles - createCandidatesAlongLineNearMidpoint will be more appropriate
644  return 0;
645  }
646 
647  // calculate lengths of segments, and work out longest straight-ish segment
648  double* segmentLengths = new double[ numberNodes-1 ]; // segments lengths distance bw pt[i] && pt[i+1]
649  double* distanceToSegment = new double[ numberNodes ]; // absolute distance bw pt[0] and pt[i] along the line
650  double totalLineLength = 0.0;
651  QVector< double > straightSegmentLengths;
652  QVector< double > straightSegmentAngles;
653  straightSegmentLengths.reserve( extremeAngleNodes.size() + 1 );
654  straightSegmentAngles.reserve( extremeAngleNodes.size() + 1 );
655  double currentStraightSegmentLength = 0;
656  double longestSegmentLength = 0;
657  int segmentIndex = 0;
658  double segmentStartX = x[0];
659  double segmentStartY = y[0];
660  for ( int i = 0; i < numberNodes - 1; i++ )
661  {
662  if ( i == 0 )
663  distanceToSegment[i] = 0;
664  else
665  distanceToSegment[i] = distanceToSegment[i-1] + segmentLengths[i-1];
666 
667  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
668  totalLineLength += segmentLengths[i];
669  if ( extremeAngleNodes.contains( i ) )
670  {
671  // at an extreme angle node, so reset counters
672  straightSegmentLengths << currentStraightSegmentLength;
673  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( atan2( y[i] - segmentStartY, x[i] - segmentStartX ) );
674  longestSegmentLength = qMax( longestSegmentLength, currentStraightSegmentLength );
675  segmentIndex++;
676  currentStraightSegmentLength = 0;
677  segmentStartX = x[i];
678  segmentStartY = y[i];
679  }
680  currentStraightSegmentLength += segmentLengths[i];
681  }
682  distanceToSegment[line->nbPoints-1] = totalLineLength;
683  straightSegmentLengths << currentStraightSegmentLength;
684  straightSegmentAngles << QgsGeometryUtils::normalizedAngle( atan2( y[numberNodes-1] - segmentStartY, x[numberNodes-1] - segmentStartX ) );
685  longestSegmentLength = qMax( longestSegmentLength, currentStraightSegmentLength );
686  double middleOfLine = totalLineLength / 2.0;
687 
688  if ( totalLineLength < labelWidth )
689  {
690  delete[] segmentLengths;
691  delete[] distanceToSegment;
692  return 0; //createCandidatesAlongLineNearMidpoint will be more appropriate
693  }
694 
695  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
696  lineStepDistance = qMin( qMin( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
697 
698  double distanceToEndOfSegment = 0.0;
699  int lastNodeInSegment = 0;
700  // finally, loop through all these straight segments. For each we create candidates along the straight segment.
701  for ( int i = 0; i < straightSegmentLengths.count(); ++i )
702  {
703  currentStraightSegmentLength = straightSegmentLengths.at( i );
704  double currentSegmentAngle = straightSegmentAngles.at( i );
705  lastNodeInSegment = extremeAngleNodes.at( i );
706  double distanceToStartOfSegment = distanceToEndOfSegment;
707  distanceToEndOfSegment = distanceToSegment[ lastNodeInSegment ];
708  double distanceToCenterOfSegment = 0.5 * ( distanceToEndOfSegment + distanceToStartOfSegment );
709 
710  if ( currentStraightSegmentLength < labelWidth )
711  // can't fit a label on here
712  continue;
713 
714  double currentDistanceAlongLine = distanceToStartOfSegment;
715  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
716  double candidateLength = 0.0;
717  double cost = 0.0;
718  double angle = 0.0;
719  double beta = 0.0;
720 
721  //calculate some cost penalties
722  double segmentCost = 1.0 - ( distanceToEndOfSegment - distanceToStartOfSegment ) / longestSegmentLength; // 0 -> 1 (lower for longer segments)
723  double segmentAngleCost = 1 - qAbs( fmod( currentSegmentAngle, M_PI ) - M_PI_2 ) / M_PI_2; // 0 -> 1, lower for more horizontal segments
724 
725  while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
726  {
727  // calculate positions along linestring corresponding to start and end of current label candidate
728  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
729  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
730 
731  candidateLength = sqrt(( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
732 
733 
734  // LOTS OF DIFFERENT COSTS TO BALANCE HERE - feel free to tweak these, but please add a unit test
735  // which covers the situation you are adjusting for (eg "given equal length lines, choose the more horizontal line")
736 
737  cost = candidateLength / labelWidth;
738  if ( cost > 0.98 )
739  cost = 0.0001;
740  else
741  {
742  // jaggy line has a greater cost
743  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
744  }
745 
746  // penalize positions which are further from the straight segments's midpoint
747  double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
748  double costCenter = 2 * qAbs( labelCenter - distanceToCenterOfSegment ) / ( distanceToEndOfSegment - distanceToStartOfSegment ); // 0 -> 1
749  cost += costCenter * 0.0005; // < 0, 0.0005 >
750 
751  if ( !closedLine )
752  {
753  // penalize positions which are further from absolute center of whole linestring
754  // this only applies to non closed linestrings, since the middle of a closed linestring is effectively arbitrary
755  // and irrelevant to labeling
756  double costLineCenter = 2 * qAbs( labelCenter - middleOfLine ) / totalLineLength; // 0 -> 1
757  cost += costLineCenter * 0.0005; // < 0, 0.0005 >
758  }
759 
760  cost += segmentCost * 0.0005; // prefer labels on longer straight segments
761  cost += segmentAngleCost * 0.0001; // prefer more horizontal segments, but this is less important than length considerations
762 
763  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
764  {
765  angle = 0.0;
766  }
767  else
768  angle = atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
769 
770  beta = angle + M_PI / 2;
771 
773  {
774  // find out whether the line direction for this candidate is from right to left
775  bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
776  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
777  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
778  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
779  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
780 
781  double placementCost = 0.0;
782  if ( belowLine )
783  {
784  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
785  {
786  lPos.append( new LabelPosition( i, candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
787  placementCost += 0.001;
788  }
789  }
790  if ( aboveLine )
791  {
792  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
793  {
794  lPos.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
795  placementCost += 0.001;
796  }
797  }
798  if ( flags & FLAG_ON_LINE )
799  {
800  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle ) )
801  lPos.append( new LabelPosition( i, candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
802  }
803  }
805  {
806  lPos.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
807  }
808  else
809  {
810  // an invalid arrangement?
811  }
812 
813  currentDistanceAlongLine += lineStepDistance;
814  }
815  }
816 
817  delete[] segmentLengths;
818  delete[] distanceToSegment;
819  return lPos.size();
820 }
821 
822 int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& lPos, PointSet* mapShape, double initialCost )
823 {
824  double distanceLineToLabel = getLabelDistance();
825 
826  double labelWidth = getLabelWidth();
827  double labelHeight = getLabelHeight();
828 
829  double angle;
830  double cost;
831 
832  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
833  if ( flags == 0 )
834  flags = FLAG_ON_LINE; // default flag
835 
836  QList<LabelPosition*> positions;
837 
838  PointSet * line = mapShape;
839  int nbPoints = line->nbPoints;
840  double *x = line->x;
841  double *y = line->y;
842 
843  double* segmentLengths = new double[nbPoints-1]; // segments lengths distance bw pt[i] && pt[i+1]
844  double* distanceToSegment = new double[nbPoints]; // absolute distance bw pt[0] and pt[i] along the line
845 
846  double totalLineLength = 0.0; // line length
847  for ( int i = 0; i < line->nbPoints - 1; i++ )
848  {
849  if ( i == 0 )
850  distanceToSegment[i] = 0;
851  else
852  distanceToSegment[i] = distanceToSegment[i-1] + segmentLengths[i-1];
853 
854  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
855  totalLineLength += segmentLengths[i];
856  }
857  distanceToSegment[line->nbPoints-1] = totalLineLength;
858 
859  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
860  double currentDistanceAlongLine = 0;
861 
862  if ( totalLineLength > labelWidth )
863  {
864  lineStepDistance = qMin( qMin( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
865  }
866  else // line length < label width => centering label position
867  {
868  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
869  lineStepDistance = -1;
870  totalLineLength = labelWidth;
871  }
872 
873  double candidateLength;
874  double beta;
875  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
876  int i = 0;
877  while ( currentDistanceAlongLine < totalLineLength - labelWidth )
878  {
879  // calculate positions along linestring corresponding to start and end of current label candidate
880  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
881  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
882 
883  if ( currentDistanceAlongLine < 0 )
884  {
885  // label is bigger than line, use whole available line
886  candidateLength = sqrt(( x[nbPoints-1] - x[0] ) * ( x[nbPoints-1] - x[0] )
887  + ( y[nbPoints-1] - y[0] ) * ( y[nbPoints-1] - y[0] ) );
888  }
889  else
890  {
891  candidateLength = sqrt(( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
892  }
893 
894  cost = candidateLength / labelWidth;
895  if ( cost > 0.98 )
896  cost = 0.0001;
897  else
898  {
899  // jaggy line has a greater cost
900  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
901  }
902 
903  // penalize positions which are further from the line's midpoint
904  double costCenter = qAbs( totalLineLength / 2 - ( currentDistanceAlongLine + labelWidth / 2 ) ) / totalLineLength; // <0, 0.5>
905  cost += costCenter / 1000; // < 0, 0.0005 >
906  cost += initialCost;
907 
908  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
909  {
910  angle = 0.0;
911  }
912  else
913  angle = atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
914 
915  beta = angle + M_PI / 2;
916 
918  {
919  // find out whether the line direction for this candidate is from right to left
920  bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
921  // meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
922  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
923  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
924  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
925 
926  if ( aboveLine )
927  {
928  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
929  positions.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
930  }
931  if ( belowLine )
932  {
933  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
934  positions.append( new LabelPosition( i, candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
935  }
936  if ( flags & FLAG_ON_LINE )
937  {
938  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle ) )
939  positions.append( new LabelPosition( i, candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
940  }
941  }
943  {
944  positions.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
945  }
946  else
947  {
948  // an invalid arrangement?
949  }
950 
951  currentDistanceAlongLine += lineStepDistance;
952 
953  i++;
954 
955  if ( lineStepDistance < 0 )
956  break;
957  }
958 
959  //delete line;
960 
961  delete[] segmentLengths;
962  delete[] distanceToSegment;
963 
964  lPos.append( positions );
965  return lPos.size();
966 }
967 
968 
969 LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int& orientation, int index, double distance, bool& reversed, bool& flip )
970 {
971  // Check that the given distance is on the given index and find the correct index and distance if not
972  while ( distance < 0 && index > 1 )
973  {
974  index--;
975  distance += path_distances[index];
976  }
977 
978  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
979  {
980  return nullptr;
981  }
982 
983  // Same thing, checking if we go off the end
984  while ( index < path_positions->nbPoints && distance > path_distances[index] )
985  {
986  distance -= path_distances[index];
987  index += 1;
988  }
989  if ( index >= path_positions->nbPoints )
990  {
991  return nullptr;
992  }
993 
994  LabelInfo* li = mLF->curvedLabelInfo();
995 
996  double string_height = li->label_height;
997 
998  double segment_length = path_distances[index];
999  if ( qgsDoubleNear( segment_length, 0.0 ) )
1000  {
1001  // Not allowed to place across on 0 length segments or discontinuities
1002  return nullptr;
1003  }
1004 
1005  if ( orientation == 0 ) // Must be map orientation
1006  {
1007  // Calculate the orientation based on the angle of the path segment under consideration
1008 
1009  double _distance = distance;
1010  int endindex = index;
1011 
1012  for ( int i = 0; i < li->char_num; i++ )
1013  {
1014  LabelInfo::CharacterInfo& ci = li->char_info[i];
1015  double start_x, start_y, end_x, end_y;
1016  if ( nextCharPosition( ci.width, path_distances[index], path_positions, endindex, _distance, start_x, start_y, end_x, end_y ) == false )
1017  {
1018  return nullptr;
1019  }
1020  }
1021 
1022  // Determine the angle of the path segment under consideration
1023  double dx = path_positions->x[endindex] - path_positions->x[index];
1024  double dy = path_positions->y[endindex] - path_positions->y[index];
1025  double line_angle = atan2( -dy, dx );
1026 
1027  bool isRightToLeft = ( line_angle > 0.55 * M_PI || line_angle < -0.45 * M_PI );
1028  reversed = isRightToLeft;
1029  orientation = isRightToLeft ? -1 : 1;
1030  }
1031 
1032  if ( !showUprightLabels() )
1033  {
1034  if ( orientation < 0 )
1035  {
1036  flip = true; // Report to the caller, that the orientation is flipped
1037  reversed = !reversed;
1038  orientation = 1;
1039  }
1040  }
1041 
1042  LabelPosition* slp = nullptr;
1043  LabelPosition* slp_tmp = nullptr;
1044 
1045  double old_x = path_positions->x[index-1];
1046  double old_y = path_positions->y[index-1];
1047 
1048  double new_x = path_positions->x[index];
1049  double new_y = path_positions->y[index];
1050 
1051  double dx = new_x - old_x;
1052  double dy = new_y - old_y;
1053 
1054  double angle = atan2( -dy, dx );
1055 
1056  for ( int i = 0; i < li->char_num; i++ )
1057  {
1058  double last_character_angle = angle;
1059 
1060  // grab the next character according to the orientation
1061  LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
1062  if ( qgsDoubleNear( ci.width, 0.0 ) )
1063  // Certain scripts rely on zero-width character, skip those to prevent failure (see #15801)
1064  continue;
1065 
1066  double start_x, start_y, end_x, end_y;
1067  if ( nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) == false )
1068  {
1069  delete slp;
1070  return nullptr;
1071  }
1072 
1073  // Calculate angle from the start of the character to the end based on start_/end_ position
1074  angle = atan2( start_y - end_y, end_x - start_x );
1075 
1076  // Test last_character_angle vs angle
1077  // since our rendering angle has changed then check against our
1078  // max allowable angle change.
1079  double angle_delta = last_character_angle - angle;
1080  // normalise between -180 and 180
1081  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1082  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1083  if (( li->max_char_angle_inside > 0 && angle_delta > 0
1084  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
1085  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1086  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
1087  {
1088  delete slp;
1089  return nullptr;
1090  }
1091 
1092  // Shift the character downwards since the draw position is specified at the baseline
1093  // and we're calculating the mean line here
1094  double dist = 0.9 * li->label_height / 2;
1095  if ( orientation < 0 )
1096  {
1097  dist = -dist;
1098  flip = true;
1099  }
1100  start_x += dist * cos( angle + M_PI_2 );
1101  start_y -= dist * sin( angle + M_PI_2 );
1102 
1103  double render_angle = angle;
1104 
1105  double render_x = start_x;
1106  double render_y = start_y;
1107 
1108  // Center the text on the line
1109  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1110  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1111 
1112  if ( orientation < 0 )
1113  {
1114  // rotate in place
1115  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
1116  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1117  render_angle += M_PI;
1118  }
1119 
1120  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1121  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1122  if ( !slp )
1123  slp = tmp;
1124  else
1125  slp_tmp->setNextPart( tmp );
1126  slp_tmp = tmp;
1127 
1128  // Normalise to 0 <= angle < 2PI
1129  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1130  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1131 
1132  if ( render_angle > M_PI / 2 && render_angle < 1.5 * M_PI )
1134  }
1135  // END FOR
1136 
1137  return slp;
1138 }
1139 
1140 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
1141 {
1142  LabelPosition* newLp = new LabelPosition( *lp );
1143  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
1144  return newLp;
1145 }
1146 
1147 int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos, PointSet* mapShape )
1148 {
1149  LabelInfo* li = mLF->curvedLabelInfo();
1150 
1151  // label info must be present
1152  if ( !li || li->char_num == 0 )
1153  return 0;
1154 
1155  // distance calculation
1156  double* path_distances = new double[mapShape->nbPoints];
1157  double total_distance = 0;
1158  double old_x = -1.0, old_y = -1.0;
1159  for ( int i = 0; i < mapShape->nbPoints; i++ )
1160  {
1161  if ( i == 0 )
1162  path_distances[i] = 0;
1163  else
1164  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
1165  old_x = mapShape->x[i];
1166  old_y = mapShape->y[i];
1167 
1168  total_distance += path_distances[i];
1169  }
1170 
1171  if ( qgsDoubleNear( total_distance, 0.0 ) )
1172  {
1173  delete[] path_distances;
1174  return 0;
1175  }
1176 
1177  QLinkedList<LabelPosition*> positions;
1178  double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );
1179 
1180  unsigned long flags = mLF->layer()->arrangementFlags();
1181  if ( flags == 0 )
1182  flags = FLAG_ON_LINE; // default flag
1183 
1184  // generate curved labels
1185  for ( double i = 0; i < total_distance; i += delta )
1186  {
1187  bool flip = false;
1188  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1189  bool reversed = false;
1190 
1191  // an orientation of 0 means try both orientations and choose the best
1192  int orientation = 0;
1193  if ( !( flags & FLAG_MAP_ORIENTATION ) )
1194  {
1195  //... but if we are using line orientation flags, then we can only accept a single orientation,
1196  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1197  orientation = 1;
1198  }
1199 
1200  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1201  if ( slp == nullptr )
1202  continue;
1203 
1204  // If we placed too many characters upside down
1205  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1206  {
1207  // if labels should be shown upright then retry with the opposite orientation
1208  if (( showUprightLabels() && !flip ) )
1209  {
1210  delete slp;
1211  orientation = -orientation;
1212  slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1213  }
1214  }
1215  if ( slp == nullptr )
1216  continue;
1217 
1218  // evaluate cost
1219  double angle_diff = 0.0, angle_last = 0.0, diff;
1220  LabelPosition* tmp = slp;
1221  double sin_avg = 0, cos_avg = 0;
1222  while ( tmp )
1223  {
1224  if ( tmp != slp ) // not first?
1225  {
1226  diff = fabs( tmp->getAlpha() - angle_last );
1227  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1228  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1229  angle_diff += diff;
1230  }
1231 
1232  sin_avg += sin( tmp->getAlpha() );
1233  cos_avg += cos( tmp->getAlpha() );
1234  angle_last = tmp->getAlpha();
1235  tmp = tmp->getNextPart();
1236  }
1237 
1238  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1239  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1240  if ( cost < 0.0001 ) cost = 0.0001;
1241 
1242  // penalize positions which are further from the line's midpoint
1243  double labelCenter = i + getLabelWidth() / 2;
1244  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1245  cost += costCenter / 1000; // < 0, 0.0005 >
1246  slp->setCost( cost );
1247 
1248  // average angle is calculated with respect to periodicity of angles
1249  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1250  bool localreversed = flip ? !reversed : reversed;
1251  // displacement - we loop through 3 times, generating above, online then below line placements successively
1252  for ( int i = 0; i <= 2; ++i )
1253  {
1254  LabelPosition* p = nullptr;
1255  if ( i == 0 && (( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1256  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1257  if ( i == 1 && flags & FLAG_ON_LINE )
1258  {
1259  p = _createCurvedCandidate( slp, angle_avg, 0 );
1260  p->setCost( p->cost() + 0.002 );
1261  }
1262  if ( i == 2 && (( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1263  {
1264  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1265  p->setCost( p->cost() + 0.001 );
1266  }
1267 
1268  if ( p && mLF->permissibleZonePrepared() )
1269  {
1270  bool within = true;
1271  LabelPosition* currentPos = p;
1272  while ( within && currentPos )
1273  {
1274  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1275  currentPos = currentPos->getNextPart();
1276  }
1277  if ( !within )
1278  {
1279  delete p;
1280  p = nullptr;
1281  }
1282  }
1283 
1284  if ( p )
1285  positions.append( p );
1286  }
1287 
1288  // delete original candidate
1289  delete slp;
1290  }
1291 
1292  int nbp = positions.size();
1293  for ( int i = 0; i < nbp; i++ )
1294  {
1295  lPos << positions.takeFirst();
1296  }
1297 
1298  delete[] path_distances;
1299 
1300  return nbp;
1301 }
1302 
1303 /*
1304  * seg 2
1305  * pt3 ____________pt2
1306  * ¦ ¦
1307  * ¦ ¦
1308  * seg 3 ¦ BBOX ¦ seg 1
1309  * ¦ ¦
1310  * ¦____________¦
1311  * pt0 seg 0 pt1
1312  *
1313  */
1314 
1315 int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, PointSet *mapShape )
1316 {
1317  int i;
1318  int j;
1319 
1320  double labelWidth = getLabelWidth();
1321  double labelHeight = getLabelHeight();
1322 
1323  QLinkedList<PointSet*> shapes_toProcess;
1324  QLinkedList<PointSet*> shapes_final;
1325 
1326  mapShape->parent = nullptr;
1327 
1328  shapes_toProcess.append( mapShape );
1329 
1330  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1331 
1332  int nbp;
1333 
1334  if ( !shapes_final.isEmpty() )
1335  {
1336  QLinkedList<LabelPosition*> positions;
1337 
1338  int id = 0; // ids for candidates
1339  double dlx, dly; // delta from label center and bottom-left corner
1340  double alpha = 0.0; // rotation for the label
1341  double px, py;
1342  double dx;
1343  double dy;
1344  int bbid;
1345  double beta;
1346  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1347  double rx, ry;
1348  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1349  j = 0;
1350 
1351  // Compute bounding box foreach finalShape
1352  while ( !shapes_final.isEmpty() )
1353  {
1354  PointSet *shape = shapes_final.takeFirst();
1355  boxes[j] = shape->compute_chull_bbox();
1356 
1357  if ( shape->parent )
1358  delete shape;
1359 
1360  j++;
1361  }
1362 
1363  //dx = dy = min( yrm, xrm ) / 2;
1364  dx = labelWidth / 2.0;
1365  dy = labelHeight / 2.0;
1366 
1367  int numTry = 0;
1368 
1369  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1370  //then use a smaller limit for number of iterations
1371  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1372 
1373  do
1374  {
1375  for ( bbid = 0; bbid < j; bbid++ )
1376  {
1377  CHullBox *box = boxes[bbid];
1378 
1379  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1380  {
1381  // Very Large BBOX (should never occur)
1382  continue;
1383  }
1384 
1386  {
1387  //check width/height of bbox is sufficient for label
1388  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1389  mLF->permissibleZone().boundingBox().height() < labelHeight )
1390  {
1391  //no way label can fit in this box, skip it
1392  continue;
1393  }
1394  }
1395 
1396  bool enoughPlace = false;
1398  {
1399  enoughPlace = true;
1400  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1401  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1402  int i, j;
1403 
1404  // Virtual label: center on bbox center, label size = 2x original size
1405  // alpha = 0.
1406  // If all corner are in bbox then place candidates horizontaly
1407  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1408  {
1409  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1410  {
1411  if ( !mapShape->containsPoint( rx, ry ) )
1412  {
1413  enoughPlace = false;
1414  break;
1415  }
1416  }
1417  if ( !enoughPlace )
1418  {
1419  break;
1420  }
1421  }
1422 
1423  } // arrangement== FREE ?
1424 
1425  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1426  {
1427  alpha = 0.0; // HORIZ
1428  }
1429  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1430  {
1431  if ( box->alpha <= M_PI / 4 )
1432  {
1433  alpha = box->alpha;
1434  }
1435  else
1436  {
1437  alpha = box->alpha - M_PI / 2;
1438  }
1439  }
1440  else if ( box->length > box->width )
1441  {
1442  alpha = box->alpha - M_PI / 2;
1443  }
1444  else
1445  {
1446  alpha = box->alpha;
1447  }
1448 
1449  beta = atan2( labelHeight, labelWidth ) + alpha;
1450 
1451 
1452  //alpha = box->alpha;
1453 
1454  // delta from label center and down-left corner
1455  dlx = cos( beta ) * diago;
1456  dly = sin( beta ) * diago;
1457 
1458  double px0, py0;
1459 
1460  px0 = box->width / 2.0;
1461  py0 = box->length / 2.0;
1462 
1463  px0 -= ceil( px0 / dx ) * dx;
1464  py0 -= ceil( py0 / dy ) * dy;
1465 
1466  for ( px = px0; px <= box->width; px += dx )
1467  {
1468  for ( py = py0; py <= box->length; py += dy )
1469  {
1470 
1471  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1472  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1473 
1474  rx += box->x[0];
1475  ry += box->y[0];
1476 
1477  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1478  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1479  : mapShape->containsPoint( rx, ry ) );
1480  if ( candidateAcceptable )
1481  {
1482  // cost is set to minimal value, evaluated later
1483  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1484  }
1485  }
1486  }
1487  } // forall box
1488 
1489  nbp = positions.size();
1490  if ( nbp == 0 )
1491  {
1492  dx /= 2;
1493  dy /= 2;
1494  numTry++;
1495  }
1496  }
1497  while ( nbp == 0 && numTry < maxTry );
1498 
1499  nbp = positions.size();
1500 
1501  for ( i = 0; i < nbp; i++ )
1502  {
1503  lPos << positions.takeFirst();
1504  }
1505 
1506  for ( bbid = 0; bbid < j; bbid++ )
1507  {
1508  delete boxes[bbid];
1509  }
1510 
1511  delete[] boxes;
1512  }
1513  else
1514  {
1515  nbp = 0;
1516  }
1517 
1518  return nbp;
1519 }
1520 
1521 int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
1522  double bboxMin[2], double bboxMax[2],
1523  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1524 {
1525  double bbox[4];
1526 
1527  bbox[0] = bboxMin[0];
1528  bbox[1] = bboxMin[1];
1529  bbox[2] = bboxMax[0];
1530  bbox[3] = bboxMax[1];
1531 
1532  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1533 
1534  if ( mLF->hasFixedPosition() )
1535  {
1536  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1537  }
1538  else
1539  {
1540  switch ( type )
1541  {
1542  case GEOS_POINT:
1544  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1546  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1547  else
1548  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1549  break;
1550  case GEOS_LINESTRING:
1551  if ( mLF->layer()->isCurved() )
1552  createCurvedCandidatesAlongLine( lPos, mapShape );
1553  else
1554  createCandidatesAlongLine( lPos, mapShape );
1555  break;
1556 
1557  case GEOS_POLYGON:
1558  switch ( mLF->layer()->arrangement() )
1559  {
1562  double cx, cy;
1563  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1565  createCandidatesOverPoint( cx, cy, lPos, angle );
1566  else
1567  createCandidatesAroundPoint( cx, cy, lPos, angle );
1568  break;
1570  createCandidatesAlongLine( lPos, mapShape );
1571  break;
1573  createCurvedCandidatesAlongLine( lPos, mapShape );
1574  break;
1575  default:
1576  createCandidatesForPolygon( lPos, mapShape );
1577  break;
1578  }
1579  }
1580  }
1581 
1582  // purge candidates that are outside the bbox
1583 
1584  QMutableListIterator< LabelPosition*> i( lPos );
1585  while ( i.hasNext() )
1586  {
1587  LabelPosition* pos = i.next();
1588  bool outside = false;
1589  if ( mLF->layer()->pal->getShowPartial() )
1590  outside = !pos->isIntersect( bbox );
1591  else
1592  outside = !pos->isInside( bbox );
1593  if ( outside )
1594  {
1595  i.remove();
1596  delete pos;
1597  }
1598  else // this one is OK
1599  {
1600  pos->insertIntoIndex( candidates );
1601  }
1602  }
1603 
1604  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1605  return lPos.count();
1606 }
1607 
1608 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1609 {
1610  if ( !mGeos )
1611  createGeosGeom();
1612 
1613  GEOSContextHandle_t ctxt = geosContext();
1614  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1615 
1616  double sizeCost = 0;
1617  if ( geomType == GEOS_LINESTRING )
1618  {
1619  double length;
1620  try
1621  {
1622  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1623  return; // failed to calculate length
1624  }
1625  catch ( GEOSException &e )
1626  {
1627  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1628  return;
1629  }
1630  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1631  if ( length >= bbox_length / 4 )
1632  return; // the line is longer than quarter of height or width - don't penalize it
1633 
1634  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1635  }
1636  else if ( geomType == GEOS_POLYGON )
1637  {
1638  double area;
1639  try
1640  {
1641  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1642  return;
1643  }
1644  catch ( GEOSException &e )
1645  {
1646  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1647  return;
1648  }
1649  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1650  if ( area >= bbox_area / 16 )
1651  return; // covers more than 1/16 of our view - don't penalize it
1652 
1653  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1654  }
1655  else
1656  return; // no size penalty for points
1657 
1658  // apply the penalty
1659  for ( int i = 0; i < nbp; i++ )
1660  {
1661  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1662  }
1663 }
1664 
1666 {
1667  if ( !p2->mGeos )
1668  p2->createGeosGeom();
1669 
1670  try
1671  {
1672  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1673  }
1674  catch ( GEOSException &e )
1675  {
1676  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1677  return false;
1678  }
1679 }
1680 
1682 {
1683  if ( !mGeos )
1684  createGeosGeom();
1685  if ( !other->mGeos )
1686  other->createGeosGeom();
1687 
1688  GEOSContextHandle_t ctxt = geosContext();
1689  try
1690  {
1691  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1692  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1693  GEOSGeometry* geoms[2] = { g1, g2 };
1694  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1695  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1696  GEOSGeom_destroy_r( ctxt, g );
1697 
1698  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1699  {
1700  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1701  GEOSGeom_destroy_r( ctxt, gTmp );
1702  return false;
1703  }
1704  invalidateGeos();
1705 
1706  // set up new geometry
1707  mGeos = gTmp;
1708  mOwnsGeom = true;
1709 
1710  deleteCoords();
1711  qDeleteAll( mHoles );
1712  mHoles.clear();
1713  extractCoords( mGeos );
1714  return true;
1715  }
1716  catch ( GEOSException &e )
1717  {
1718  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1719  return false;
1720  }
1721 }
1722 
1724 {
1725  if ( mLF->alwaysShow() )
1726  {
1727  //if feature is set to always show, bump the priority up by orders of magnitude
1728  //so that other feature's labels are unlikely to be placed over the label for this feature
1729  //(negative numbers due to how pal::extract calculates inactive cost)
1730  return -0.2;
1731  }
1732 
1733  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1734 }
1735 
1737 {
1738  bool uprightLabel = false;
1739 
1740  switch ( mLF->layer()->upsidedownLabels() )
1741  {
1742  case Layer::Upright:
1743  uprightLabel = true;
1744  break;
1745  case Layer::ShowDefined:
1746  // upright only dynamic labels
1747  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
1748  {
1749  uprightLabel = true;
1750  }
1751  break;
1752  case Layer::ShowAll:
1753  break;
1754  default:
1755  uprightLabel = true;
1756  }
1757  return uprightLabel;
1758 }
1759 
1760 bool FeaturePart::nextCharPosition( double charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
1761  double& start_x, double& start_y, double& end_x, double& end_y ) const
1762 {
1763  // Coordinates this character will start at
1764  if ( qgsDoubleNear( segment_length, 0.0 ) )
1765  {
1766  // Not allowed to place across on 0 length segments or discontinuities
1767  return false;
1768  }
1769 
1770  double old_x = path_positions->x[index-1];
1771  double old_y = path_positions->y[index-1];
1772 
1773  double new_x = path_positions->x[index];
1774  double new_y = path_positions->y[index];
1775 
1776  double dx = new_x - old_x;
1777  double dy = new_y - old_y;
1778 
1779  start_x = old_x + dx * distance / segment_length;
1780  start_y = old_y + dy * distance / segment_length;
1781 
1782  // Coordinates this character ends at, calculated below
1783  end_x = 0;
1784  end_y = 0;
1785 
1786  if ( segment_length - distance >= charWidth )
1787  {
1788  // if the distance remaining in this segment is enough, we just go further along the segment
1789  distance += charWidth;
1790  end_x = old_x + dx * distance / segment_length;
1791  end_y = old_y + dy * distance / segment_length;
1792  }
1793  else
1794  {
1795  // If there isn't enough distance left on this segment
1796  // then we need to search until we find the line segment that ends further than ci.width away
1797  do
1798  {
1799  old_x = new_x;
1800  old_y = new_y;
1801  index++;
1802  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1803  {
1804  return false;
1805  }
1806  new_x = path_positions->x[index];
1807  new_y = path_positions->y[index];
1808  dx = new_x - old_x;
1809  dy = new_y - old_y;
1810  }
1811  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_
1812 
1813  // Calculate the position to place the end of the character on
1814  GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );
1815 
1816  // Need to calculate distance on the new segment
1817  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1818  }
1819  return true;
1820 }
Label below point, slightly right of center.
pal::Layer * layer() const
Get PAL layer of the label feature. Should be only used internally in PAL.
UpsideDownLabels upsidedownLabels() const
Returns how upside down labels are handled within the layer.
Definition: layer.h:205
double length
Definition: pointset.h:55
static unsigned index
Label on bottom-left of point.
double y
Definition: qgspoint.h:116
virtual ~FeaturePart()
Delete the feature.
Definition: feature.cpp:81
int upsideDownCharCount() const
Returns the number of upside down characters for this label position.
void invalidateGeos()
Definition: pointset.cpp:204
bool isIntersect(double *bbox)
Is the labelposition intersect the bounding-box ?
static bool candidateSortGrow(const LabelPosition *c1, const LabelPosition *c2)
Sorts label candidates in ascending order of cost.
bool hasFixedPosition() const
Returns true if the feature&#39;s label has a fixed position.
Definition: feature.h:234
QList< FeaturePart * > mHoles
Definition: feature.h:281
double max_char_angle_outside
Definition: feature.h:70
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
double getLabelHeight() const
Definition: feature.h:224
double fixedAngle() const
Returns the fixed angle for the feature&#39;s label.
Definition: feature.h:231
double getWidth() const
Label on top-left of point.
void setCost(double newCost)
Sets the candidate label position&#39;s geographical cost.
int incrementUpsideDownCharCount()
Increases the count of upside down characters for this label position.
A set of features which influence the labelling process.
Definition: layer.h:59
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode...
int createCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for line feature.
Definition: feature.cpp:589
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label&#39;s permissibleZone().
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
int createCandidatesAroundPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generate candidates for point feature, located around a specified point.
Definition: feature.cpp:436
Candidates are placed in predefined positions around a point. Peference is given to positions with gr...
double cost() const
Returns the candidate label position&#39;s geographical cost.
friend class LabelPosition
Definition: pointset.h:66
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
Label on top of point, slightly right of center.
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
void createGeosGeom() const
Definition: pointset.cpp:150
double getLabelWidth() const
Definition: feature.h:223
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:172
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1681
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:219
double priority() const
Returns the feature&#39;s labeling priority.
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1665
void addSizePenalty(int nbp, QList< LabelPosition * > &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1608
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:769
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:196
#define M_PI_2
Definition: util.cpp:45
int createCurvedCandidatesAlongLine(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:1147
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
Arranges candidates following the curvature of a line feature. Applies to line layers only...
CharacterInfo * char_info
Definition: feature.h:73
pal::LabelInfo * curvedLabelInfo() const
Get additional infor required for curved label placement. Returns null if not set.
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
QgsPalLayerSettings::Placement arrangement() const
Returns the layer&#39;s arrangement policy.
Definition: layer.h:95
double width
Definition: pointset.h:54
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:192
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int &orientation, int index, double distance, bool &reversed, bool &flip)
Returns the label position for a curved label at a specific offset along a path.
Definition: feature.cpp:969
QgsGeometry permissibleZone() const
Returns the label&#39;s permissible zone geometry.
Label on left of point.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
bool getShowPartial()
Get flag show partial label.
Definition: pal.cpp:624
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py)
Get a point a set distance along a line geometry.
Definition: pointset.cpp:811
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1723
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:160
PointSet * parent
Definition: pointset.h:161
double getLabelDistance() const
Definition: feature.h:225
double * x
Definition: pointset.h:152
double ymax
Definition: pointset.h:175
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
double xmin
Definition: pointset.h:172
PointSet * holeOf
Definition: pointset.h:160
QgsPoint fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
static LabelPosition * _createCurvedCandidate(LabelPosition *lp, double angle, double dist)
Definition: feature.cpp:1140
double ymin
Definition: pointset.h:174
int createCandidatesAlongLineNearStraightSegments(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for line feature, by trying to place candidates towards the middle of the longest...
Definition: feature.cpp:602
double getY(int i=0) const
get the down-left y coordinate
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
double bottom
Bottom margin.
static void logMessage(const QString &message, const QString &tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
Optional additional info about label (for curved labels)
Definition: feature.h:50
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:150
int createCandidatesOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:231
bool hasFixedRotation() const
Returns true if the feature&#39;s label has a fixed rotation.
Definition: feature.h:228
bool nextCharPosition(double charWidth, double segment_length, PointSet *path_positions, int &index, double &distance, double &start_x, double &start_y, double &end_x, double &end_y) const
Returns true if the next char position is found. The referenced parameters are updated.
Definition: feature.cpp:1760
double label_height
Definition: feature.h:71
double length() const
Returns length of line geometry.
Definition: pointset.cpp:858
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:62
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1736
LabelPosition * getNextPart() const
GEOSContextHandle_t geosContext()
Get GEOS context handle to be used in all GEOS library calls with reentrant API.
Definition: pal.cpp:48
Stores visual margins for labels (left, right, top and bottom)
void deleteCoords()
Definition: pointset.cpp:232
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
double getX(int i=0) const
get the down-left x coordinate
Main class to handle feature.
Definition: feature.h:89
Offset distance applies from rendered symbol bounds.
void setPartId(int id)
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:268
double * y
Definition: pointset.h:153
static void splitPolygons(QLinkedList< PointSet * > &shapes_toProcess, QLinkedList< PointSet * > &shapes_final, double xrm, double yrm)
Split a concave shape into several convex shapes.
Definition: pointset.cpp:295
double getAlpha() const
get alpha
const VisualMargin & visualMargin() const
Returns the visual margin for the label feature.
int createCandidatesForPolygon(QList< LabelPosition * > &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1315
Pal * pal
Definition: layer.h:253
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:572
int createCandidatesAlongLineNearMidpoint(QList< LabelPosition * > &lPos, PointSet *mapShape, double initialCost=0.0)
Generate candidates for line feature, by trying to place candidates as close as possible to the line&#39;...
Definition: feature.cpp:822
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
QgsPoint positionOffset() const
Applies only to "offset from point" placement strategy.
int createCandidates(QList< LabelPosition * > &lPos, double bboxMin[2], double bboxMax[2], PointSet *mapShape, RTree< LabelPosition *, double, 2, double > *candidates)
Generic method to generate label candidates for the feature.
Definition: feature.cpp:1521
double x[4]
Definition: pointset.h:49
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
int connectedFeatureId(QgsFeatureId featureId) const
Returns the connected feature ID for a label feature ID, which is unique for all features which have ...
Definition: layer.cpp:414
double getHeight() const
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
double y[4]
Definition: pointset.h:50
double max_char_angle_inside
Definition: feature.h:69
double right
Right margin.
int createCandidatesAtOrderedPositionsOverPoint(double x, double y, QList< LabelPosition * > &lPos, double angle)
Generates candidates following a prioritised list of predefined positions around a point...
Definition: feature.cpp:310
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:89
double distLabel() const
Applies to "around point" placement strategy or linestring features.
GEOSGeometry * mGeos
Definition: pointset.h:148
LabelPosition is a candidate feature label position.
Definition: labelposition.h:51
QString name() const
Returns the layer&#39;s name.
Definition: layer.h:90
LineArrangementFlags arrangementFlags() const
Returns the layer&#39;s arrangement flags.
Definition: layer.h:110
QgsLabelFeature * mLF
Definition: feature.h:280
Label on top of point, slightly left of center.
qint64 QgsFeatureId
Definition: qgsfeature.h:32
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:61
Label on right of point.
double alpha
Definition: pointset.h:52
static int reorderPolygon(int nbPoints, double *x, double *y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside...
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
Definition: feature.cpp:53
#define M_PI
Definition: feature.cpp:48
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:211
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:99
double xmax
Definition: pointset.h:173
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cc:857
int char_num
Definition: feature.h:72
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
bool mOwnsGeom
Definition: pointset.h:149
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:216
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:155
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
double x
Definition: qgspoint.h:115