QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
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 
311 {
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 
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 
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 
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 
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  double start_x, start_y, end_x, end_y;
1063  if ( nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) == false )
1064  {
1065  delete slp;
1066  return nullptr;
1067  }
1068 
1069  // Calculate angle from the start of the character to the end based on start_/end_ position
1070  angle = atan2( start_y - end_y, end_x - start_x );
1071 
1072  // Test last_character_angle vs angle
1073  // since our rendering angle has changed then check against our
1074  // max allowable angle change.
1075  double angle_delta = last_character_angle - angle;
1076  // normalise between -180 and 180
1077  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1078  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1079  if (( li->max_char_angle_inside > 0 && angle_delta > 0
1080  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
1081  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1082  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
1083  {
1084  delete slp;
1085  return nullptr;
1086  }
1087 
1088  // Shift the character downwards since the draw position is specified at the baseline
1089  // and we're calculating the mean line here
1090  double dist = 0.9 * li->label_height / 2;
1091  if ( orientation < 0 )
1092  {
1093  dist = -dist;
1094  flip = true;
1095  }
1096  start_x += dist * cos( angle + M_PI_2 );
1097  start_y -= dist * sin( angle + M_PI_2 );
1098 
1099  double render_angle = angle;
1100 
1101  double render_x = start_x;
1102  double render_y = start_y;
1103 
1104  // Center the text on the line
1105  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1106  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1107 
1108  if ( orientation < 0 )
1109  {
1110  // rotate in place
1111  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
1112  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1113  render_angle += M_PI;
1114  }
1115 
1116  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1117  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1118  if ( !slp )
1119  slp = tmp;
1120  else
1121  slp_tmp->setNextPart( tmp );
1122  slp_tmp = tmp;
1123 
1124  // Normalise to 0 <= angle < 2PI
1125  while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
1126  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1127 
1128  if ( render_angle > M_PI / 2 && render_angle < 1.5 * M_PI )
1130  }
1131  // END FOR
1132 
1133  return slp;
1134 }
1135 
1136 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
1137 {
1138  LabelPosition* newLp = new LabelPosition( *lp );
1139  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
1140  return newLp;
1141 }
1142 
1144 {
1145  LabelInfo* li = mLF->curvedLabelInfo();
1146 
1147  // label info must be present
1148  if ( !li || li->char_num == 0 )
1149  return 0;
1150 
1151  // distance calculation
1152  double* path_distances = new double[mapShape->nbPoints];
1153  double total_distance = 0;
1154  double old_x = -1.0, old_y = -1.0;
1155  for ( int i = 0; i < mapShape->nbPoints; i++ )
1156  {
1157  if ( i == 0 )
1158  path_distances[i] = 0;
1159  else
1160  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
1161  old_x = mapShape->x[i];
1162  old_y = mapShape->y[i];
1163 
1164  total_distance += path_distances[i];
1165  }
1166 
1167  if ( qgsDoubleNear( total_distance, 0.0 ) )
1168  {
1169  delete[] path_distances;
1170  return 0;
1171  }
1172 
1173  QLinkedList<LabelPosition*> positions;
1174  double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );
1175 
1176  unsigned long flags = mLF->layer()->arrangementFlags();
1177  if ( flags == 0 )
1178  flags = FLAG_ON_LINE; // default flag
1179 
1180  // generate curved labels
1181  for ( double i = 0; i < total_distance; i += delta )
1182  {
1183  bool flip = false;
1184  // placements may need to be reversed if using map orientation and the line has right-to-left direction
1185  bool reversed = false;
1186 
1187  // an orientation of 0 means try both orientations and choose the best
1188  int orientation = 0;
1189  if ( !( flags & FLAG_MAP_ORIENTATION ) )
1190  {
1191  //... but if we are using line orientation flags, then we can only accept a single orientation,
1192  // as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1193  orientation = 1;
1194  }
1195 
1196  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1197  if ( slp == nullptr )
1198  continue;
1199 
1200  // If we placed too many characters upside down
1201  if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
1202  {
1203  // if labels should be shown upright then retry with the opposite orientation
1204  if (( showUprightLabels() && !flip ) )
1205  {
1206  delete slp;
1207  orientation = -orientation;
1208  slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i, reversed, flip );
1209  }
1210  }
1211  if ( slp == nullptr )
1212  continue;
1213 
1214  // evaluate cost
1215  double angle_diff = 0.0, angle_last = 0.0, diff;
1216  LabelPosition* tmp = slp;
1217  double sin_avg = 0, cos_avg = 0;
1218  while ( tmp )
1219  {
1220  if ( tmp != slp ) // not first?
1221  {
1222  diff = fabs( tmp->getAlpha() - angle_last );
1223  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1224  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1225  angle_diff += diff;
1226  }
1227 
1228  sin_avg += sin( tmp->getAlpha() );
1229  cos_avg += cos( tmp->getAlpha() );
1230  angle_last = tmp->getAlpha();
1231  tmp = tmp->getNextPart();
1232  }
1233 
1234  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1235  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1236  if ( cost < 0.0001 ) cost = 0.0001;
1237 
1238  // penalize positions which are further from the line's midpoint
1239  double labelCenter = i + getLabelWidth() / 2;
1240  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1241  cost += costCenter / 1000; // < 0, 0.0005 >
1242  slp->setCost( cost );
1243 
1244  // average angle is calculated with respect to periodicity of angles
1245  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1246  bool localreversed = flip ? !reversed : reversed;
1247  // displacement - we loop through 3 times, generating above, online then below line placements successively
1248  for ( int i = 0; i <= 2; ++i )
1249  {
1250  LabelPosition* p = nullptr;
1251  if ( i == 0 && (( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1252  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1253  if ( i == 1 && flags & FLAG_ON_LINE )
1254  {
1255  p = _createCurvedCandidate( slp, angle_avg, 0 );
1256  p->setCost( p->cost() + 0.002 );
1257  }
1258  if ( i == 2 && (( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1259  {
1260  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1261  p->setCost( p->cost() + 0.001 );
1262  }
1263 
1264  if ( p && mLF->permissibleZonePrepared() )
1265  {
1266  bool within = true;
1267  LabelPosition* currentPos = p;
1268  while ( within && currentPos )
1269  {
1270  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1271  currentPos = currentPos->getNextPart();
1272  }
1273  if ( !within )
1274  {
1275  delete p;
1276  p = nullptr;
1277  }
1278  }
1279 
1280  if ( p )
1281  positions.append( p );
1282  }
1283 
1284  // delete original candidate
1285  delete slp;
1286  }
1287 
1288  int nbp = positions.size();
1289  for ( int i = 0; i < nbp; i++ )
1290  {
1291  lPos << positions.takeFirst();
1292  }
1293 
1294  delete[] path_distances;
1295 
1296  return nbp;
1297 }
1298 
1299 /*
1300  * seg 2
1301  * pt3 ____________pt2
1302  * ¦ ¦
1303  * ¦ ¦
1304  * seg 3 ¦ BBOX ¦ seg 1
1305  * ¦ ¦
1306  * ¦____________¦
1307  * pt0 seg 0 pt1
1308  *
1309  */
1310 
1312 {
1313  int i;
1314  int j;
1315 
1316  double labelWidth = getLabelWidth();
1317  double labelHeight = getLabelHeight();
1318 
1319  QLinkedList<PointSet*> shapes_toProcess;
1320  QLinkedList<PointSet*> shapes_final;
1321 
1322  mapShape->parent = nullptr;
1323 
1324  shapes_toProcess.append( mapShape );
1325 
1326  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1327 
1328  int nbp;
1329 
1330  if ( !shapes_final.isEmpty() )
1331  {
1332  QLinkedList<LabelPosition*> positions;
1333 
1334  int id = 0; // ids for candidates
1335  double dlx, dly; // delta from label center and bottom-left corner
1336  double alpha = 0.0; // rotation for the label
1337  double px, py;
1338  double dx;
1339  double dy;
1340  int bbid;
1341  double beta;
1342  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1343  double rx, ry;
1344  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1345  j = 0;
1346 
1347  // Compute bounding box foreach finalShape
1348  while ( !shapes_final.isEmpty() )
1349  {
1350  PointSet *shape = shapes_final.takeFirst();
1351  boxes[j] = shape->compute_chull_bbox();
1352 
1353  if ( shape->parent )
1354  delete shape;
1355 
1356  j++;
1357  }
1358 
1359  //dx = dy = min( yrm, xrm ) / 2;
1360  dx = labelWidth / 2.0;
1361  dy = labelHeight / 2.0;
1362 
1363  int numTry = 0;
1364 
1365  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1366  //then use a smaller limit for number of iterations
1367  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1368 
1369  do
1370  {
1371  for ( bbid = 0; bbid < j; bbid++ )
1372  {
1373  CHullBox *box = boxes[bbid];
1374 
1375  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1376  {
1377  // Very Large BBOX (should never occur)
1378  continue;
1379  }
1380 
1382  {
1383  //check width/height of bbox is sufficient for label
1384  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1385  mLF->permissibleZone().boundingBox().height() < labelHeight )
1386  {
1387  //no way label can fit in this box, skip it
1388  continue;
1389  }
1390  }
1391 
1392  bool enoughPlace = false;
1394  {
1395  enoughPlace = true;
1396  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1397  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1398  int i, j;
1399 
1400  // Virtual label: center on bbox center, label size = 2x original size
1401  // alpha = 0.
1402  // If all corner are in bbox then place candidates horizontaly
1403  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1404  {
1405  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1406  {
1407  if ( !mapShape->containsPoint( rx, ry ) )
1408  {
1409  enoughPlace = false;
1410  break;
1411  }
1412  }
1413  if ( !enoughPlace )
1414  {
1415  break;
1416  }
1417  }
1418 
1419  } // arrangement== FREE ?
1420 
1421  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1422  {
1423  alpha = 0.0; // HORIZ
1424  }
1425  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1426  {
1427  if ( box->alpha <= M_PI / 4 )
1428  {
1429  alpha = box->alpha;
1430  }
1431  else
1432  {
1433  alpha = box->alpha - M_PI / 2;
1434  }
1435  }
1436  else if ( box->length > box->width )
1437  {
1438  alpha = box->alpha - M_PI / 2;
1439  }
1440  else
1441  {
1442  alpha = box->alpha;
1443  }
1444 
1445  beta = atan2( labelHeight, labelWidth ) + alpha;
1446 
1447 
1448  //alpha = box->alpha;
1449 
1450  // delta from label center and down-left corner
1451  dlx = cos( beta ) * diago;
1452  dly = sin( beta ) * diago;
1453 
1454  double px0, py0;
1455 
1456  px0 = box->width / 2.0;
1457  py0 = box->length / 2.0;
1458 
1459  px0 -= ceil( px0 / dx ) * dx;
1460  py0 -= ceil( py0 / dy ) * dy;
1461 
1462  for ( px = px0; px <= box->width; px += dx )
1463  {
1464  for ( py = py0; py <= box->length; py += dy )
1465  {
1466 
1467  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1468  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1469 
1470  rx += box->x[0];
1471  ry += box->y[0];
1472 
1473  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1474  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1475  : mapShape->containsPoint( rx, ry ) );
1476  if ( candidateAcceptable )
1477  {
1478  // cost is set to minimal value, evaluated later
1479  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1480  }
1481  }
1482  }
1483  } // forall box
1484 
1485  nbp = positions.size();
1486  if ( nbp == 0 )
1487  {
1488  dx /= 2;
1489  dy /= 2;
1490  numTry++;
1491  }
1492  }
1493  while ( nbp == 0 && numTry < maxTry );
1494 
1495  nbp = positions.size();
1496 
1497  for ( i = 0; i < nbp; i++ )
1498  {
1499  lPos << positions.takeFirst();
1500  }
1501 
1502  for ( bbid = 0; bbid < j; bbid++ )
1503  {
1504  delete boxes[bbid];
1505  }
1506 
1507  delete[] boxes;
1508  }
1509  else
1510  {
1511  nbp = 0;
1512  }
1513 
1514  return nbp;
1515 }
1516 
1518  double bboxMin[2], double bboxMax[2],
1519  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1520 {
1521  double bbox[4];
1522 
1523  bbox[0] = bboxMin[0];
1524  bbox[1] = bboxMin[1];
1525  bbox[2] = bboxMax[0];
1526  bbox[3] = bboxMax[1];
1527 
1528  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1529 
1530  if ( mLF->hasFixedPosition() )
1531  {
1532  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1533  }
1534  else
1535  {
1536  switch ( type )
1537  {
1538  case GEOS_POINT:
1540  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1542  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1543  else
1544  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1545  break;
1546  case GEOS_LINESTRING:
1547  if ( mLF->layer()->isCurved() )
1548  createCurvedCandidatesAlongLine( lPos, mapShape );
1549  else
1550  createCandidatesAlongLine( lPos, mapShape );
1551  break;
1552 
1553  case GEOS_POLYGON:
1554  switch ( mLF->layer()->arrangement() )
1555  {
1558  double cx, cy;
1559  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1561  createCandidatesOverPoint( cx, cy, lPos, angle );
1562  else
1563  createCandidatesAroundPoint( cx, cy, lPos, angle );
1564  break;
1566  createCandidatesAlongLine( lPos, mapShape );
1567  break;
1569  createCurvedCandidatesAlongLine( lPos, mapShape );
1570  break;
1571  default:
1572  createCandidatesForPolygon( lPos, mapShape );
1573  break;
1574  }
1575  }
1576  }
1577 
1578  // purge candidates that are outside the bbox
1579 
1581  while ( i.hasNext() )
1582  {
1583  LabelPosition* pos = i.next();
1584  bool outside = false;
1585  if ( mLF->layer()->pal->getShowPartial() )
1586  outside = !pos->isIntersect( bbox );
1587  else
1588  outside = !pos->isInside( bbox );
1589  if ( outside )
1590  {
1591  i.remove();
1592  delete pos;
1593  }
1594  else // this one is OK
1595  {
1596  pos->insertIntoIndex( candidates );
1597  }
1598  }
1599 
1600  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1601  return lPos.count();
1602 }
1603 
1604 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1605 {
1606  if ( !mGeos )
1607  createGeosGeom();
1608 
1609  GEOSContextHandle_t ctxt = geosContext();
1610  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1611 
1612  double sizeCost = 0;
1613  if ( geomType == GEOS_LINESTRING )
1614  {
1615  double length;
1616  try
1617  {
1618  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1619  return; // failed to calculate length
1620  }
1621  catch ( GEOSException &e )
1622  {
1623  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1624  return;
1625  }
1626  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1627  if ( length >= bbox_length / 4 )
1628  return; // the line is longer than quarter of height or width - don't penalize it
1629 
1630  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1631  }
1632  else if ( geomType == GEOS_POLYGON )
1633  {
1634  double area;
1635  try
1636  {
1637  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1638  return;
1639  }
1640  catch ( GEOSException &e )
1641  {
1642  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1643  return;
1644  }
1645  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1646  if ( area >= bbox_area / 16 )
1647  return; // covers more than 1/16 of our view - don't penalize it
1648 
1649  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1650  }
1651  else
1652  return; // no size penalty for points
1653 
1654  // apply the penalty
1655  for ( int i = 0; i < nbp; i++ )
1656  {
1657  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1658  }
1659 }
1660 
1662 {
1663  if ( !p2->mGeos )
1664  p2->createGeosGeom();
1665 
1666  try
1667  {
1668  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1669  }
1670  catch ( GEOSException &e )
1671  {
1672  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1673  return false;
1674  }
1675 }
1676 
1678 {
1679  if ( !mGeos )
1680  createGeosGeom();
1681  if ( !other->mGeos )
1682  other->createGeosGeom();
1683 
1684  GEOSContextHandle_t ctxt = geosContext();
1685  try
1686  {
1687  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1688  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1689  GEOSGeometry* geoms[2] = { g1, g2 };
1690  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1691  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1692  GEOSGeom_destroy_r( ctxt, g );
1693 
1694  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1695  {
1696  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1697  GEOSGeom_destroy_r( ctxt, gTmp );
1698  return false;
1699  }
1700  invalidateGeos();
1701 
1702  // set up new geometry
1703  mGeos = gTmp;
1704  mOwnsGeom = true;
1705 
1706  deleteCoords();
1707  qDeleteAll( mHoles );
1708  mHoles.clear();
1709  extractCoords( mGeos );
1710  return true;
1711  }
1712  catch ( GEOSException &e )
1713  {
1714  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1715  return false;
1716  }
1717 }
1718 
1720 {
1721  if ( mLF->alwaysShow() )
1722  {
1723  //if feature is set to always show, bump the priority up by orders of magnitude
1724  //so that other feature's labels are unlikely to be placed over the label for this feature
1725  //(negative numbers due to how pal::extract calculates inactive cost)
1726  return -0.2;
1727  }
1728 
1729  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1730 }
1731 
1733 {
1734  bool uprightLabel = false;
1735 
1736  switch ( mLF->layer()->upsidedownLabels() )
1737  {
1738  case Layer::Upright:
1739  uprightLabel = true;
1740  break;
1741  case Layer::ShowDefined:
1742  // upright only dynamic labels
1743  if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
1744  {
1745  uprightLabel = true;
1746  }
1747  break;
1748  case Layer::ShowAll:
1749  break;
1750  default:
1751  uprightLabel = true;
1752  }
1753  return uprightLabel;
1754 }
1755 
1756 bool FeaturePart::nextCharPosition( double charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
1757  double& start_x, double& start_y, double& end_x, double& end_y ) const
1758 {
1759  // Coordinates this character will start at
1760  if ( qgsDoubleNear( segment_length, 0.0 ) )
1761  {
1762  // Not allowed to place across on 0 length segments or discontinuities
1763  return false;
1764  }
1765 
1766  double old_x = path_positions->x[index-1];
1767  double old_y = path_positions->y[index-1];
1768 
1769  double new_x = path_positions->x[index];
1770  double new_y = path_positions->y[index];
1771 
1772  double dx = new_x - old_x;
1773  double dy = new_y - old_y;
1774 
1775  start_x = old_x + dx * distance / segment_length;
1776  start_y = old_y + dy * distance / segment_length;
1777 
1778  // Coordinates this character ends at, calculated below
1779  end_x = 0;
1780  end_y = 0;
1781 
1782  if ( segment_length - distance >= charWidth )
1783  {
1784  // if the distance remaining in this segment is enough, we just go further along the segment
1785  distance += charWidth;
1786  end_x = old_x + dx * distance / segment_length;
1787  end_y = old_y + dy * distance / segment_length;
1788  }
1789  else
1790  {
1791  // If there isn't enough distance left on this segment
1792  // then we need to search until we find the line segment that ends further than ci.width away
1793  do
1794  {
1795  old_x = new_x;
1796  old_y = new_y;
1797  index++;
1798  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1799  {
1800  return false;
1801  }
1802  new_x = path_positions->x[index];
1803  new_y = path_positions->y[index];
1804  dx = new_x - old_x;
1805  dy = new_y - old_y;
1806  }
1807  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_
1808 
1809  // Calculate the position to place the end of the character on
1810  GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );
1811 
1812  // Need to calculate distance on the new segment
1813  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1814  }
1815  return true;
1816 }
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:1517
Label below point, slightly right of center.
bool centroidInside() const
Returns whether labels placed at the centroid of features within the layer are forced to be placed in...
Definition: layer.h:218
double length
Definition: pointset.h:56
static unsigned index
Label on bottom-left of point.
virtual ~FeaturePart()
Delete the feature.
Definition: feature.cpp:81
int createCandidatesForPolygon(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1311
void invalidateGeos()
Definition: pointset.cpp:204
double distLabel() const
Applies to "around point" placement strategy or linestring features.
bool isIntersect(double *bbox)
Is the labelposition intersect the bounding-box ?
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:268
double fixedAngle() const
Returns the fixed angle for the feature&#39;s label.
Definition: feature.h:232
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:155
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
static bool candidateSortGrow(const LabelPosition *c1, const LabelPosition *c2)
Sorts label candidates in ascending order of cost.
QList< FeaturePart * > mHoles
Definition: feature.h:282
double max_char_angle_outside
Definition: feature.h:72
double priority() const
Returns the feature&#39;s labeling priority.
bool alwaysShow() const
Whether label should be always shown (sets very high label priority)
Label on top-left of point.
void setCost(double newCost)
Sets the candidate label position&#39;s geographical cost.
int incrementUpsideDownCharCount()
Increases the count of upside down characters for this label position.
bool hasFixedRotation() const
Returns true if the feature&#39;s label has a fixed rotation.
Definition: feature.h:229
double getY(int i=0) const
get the down-left y coordinate
QgsPoint positionOffset() const
Applies only to "offset from point" placement strategy.
A set of features which influence the labelling process.
Definition: layer.h:58
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode...
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
const T & at(int i) const
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
void createGeosGeom() const
Definition: pointset.cpp:150
Candidates are placed in predefined positions around a point.
friend class LabelPosition
Definition: pointset.h:67
const GEOSPreparedGeometry * permissibleZonePrepared() const
Returns a GEOS prepared geometry representing the label&#39;s permissibleZone().
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
Label on top of point, slightly right of center.
const QSizeF & symbolSize() const
Returns the size of the rendered symbol associated with this feature, if applicable.
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1677
UpsideDownLabels upsidedownLabels() const
Returns how upside down labels are handled within the layer.
Definition: layer.h:204
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1661
QgsPoint fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
double getLabelDistance() const
Definition: feature.h:226
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
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
int size() const
#define M_PI_2
Definition: util.cpp:45
double y() const
Get the y value of the point.
Definition: qgspoint.h:193
Arranges candidates following the curvature of a line feature.
CharacterInfo * char_info
Definition: feature.h:75
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
double width
Definition: pointset.h:55
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
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:171
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
double cost() const
Returns the candidate label position&#39;s geographical cost.
int count(const T &value) const
pal::LabelInfo * curvedLabelInfo() const
Get additional infor required for curved label placement. Returns null if not set.
qreal x() const
qreal y() const
bool contains(const T &value) const
void append(const T &value)
bool hasFixedQuadrant() const
Returns whether the quadrant for the label is fixed.
Label on left of point.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
QgsGeometry permissibleZone() const
Returns the label&#39;s permissible zone geometry.
bool nextCharPosition(double charWidth, double segment_length, PointSet *path_positions, int &index, double &distance, double &start_x, double &start_y, double &end_x, double &end_y) const
Returns true if the next char position is found. The referenced parameters are updated.
Definition: feature.cpp:1756
bool getShowPartial()
Get flag show partial label.
Definition: pal.cpp:624
bool isEmpty() const
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
void addSizePenalty(int nbp, QList< LabelPosition *> &lPos, double bbx[4], double bby[4])
Definition: feature.cpp:1604
PointSet * parent
Definition: pointset.h:162
double * x
Definition: pointset.h:153
double ymax
Definition: pointset.h:176
double xmin
Definition: pointset.h:173
double getHeight() const
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:207
bool isEmpty() const
PointSet * holeOf
Definition: pointset.h:161
static LabelPosition * _createCurvedCandidate(LabelPosition *lp, double angle, double dist)
Definition: feature.cpp:1136
double ymin
Definition: pointset.h:175
Arranges candidates in a circle around a point (or centroid of a polygon).
LabelPosition * getNextPart() const
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:52
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:150
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1719
QgsPalLayerSettings::Placement arrangement() const
Returns the layer&#39;s arrangement policy.
Definition: layer.h:94
bool hasFixedAngle() const
Whether the label should use a fixed angle instead of using angle from automatic placement.
double label_height
Definition: feature.h:73
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:61
pal::Layer * layer() const
Get PAL layer of the label feature. Should be only used internally in PAL.
int createCandidatesAlongLine(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for line feature.
Definition: feature.cpp:589
GEOSContextHandle_t geosContext()
Get GEOS context handle to be used in all GEOS library calls with reentrant API.
Definition: pal.cpp:48
double fixedAngle() const
Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true) ...
double getLabelHeight() const
Definition: feature.h:225
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.
Main class to handle feature.
Definition: feature.h:91
int upsideDownCharCount() const
Returns the number of upside down characters for this label position.
Offset distance applies from rendered symbol bounds.
void setPartId(int id)
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
iterator end()
bool hasFixedPosition() const
Returns true if the feature&#39;s label has a fixed position.
Definition: feature.h:235
void reserve(int size)
int createCandidatesOverPoint(double x, double y, QList< LabelPosition *> &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:231
double * y
Definition: pointset.h:154
bool hasNext() const
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
const VisualMargin & visualMargin() const
Returns the visual margin for the label feature.
Pal * pal
Definition: layer.h:252
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:572
const T & at(int i) const
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
bool hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
double x[4]
Definition: pointset.h:50
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:160
double getAlpha() const
get alpha
double length() const
Returns length of line geometry.
Definition: pointset.cpp:858
QgsPalLayerSettings::OffsetType offsetType() const
Returns the offset type, which determines how offsets and distance to label behaves.
double getWidth() const
double getX(int i=0) const
get the down-left x coordinate
double y[4]
Definition: pointset.h:51
bool isEmpty() const
double max_char_angle_inside
Definition: feature.h:71
double right
Right margin.
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
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
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:89
int count(const T &value) const
QString name() const
Returns the layer&#39;s name.
Definition: layer.h:89
double getLabelWidth() const
Definition: feature.h:224
GEOSGeometry * mGeos
Definition: pointset.h:149
LabelPosition is a candidate feature label position.
Definition: labelposition.h:51
QgsLabelFeature * mLF
Definition: feature.h:281
Label on top of point, slightly left of center.
qint64 QgsFeatureId
Definition: qgsfeature.h:31
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:61
Label on right of point.
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:192
LineArrangementFlags arrangementFlags() const
Returns the layer&#39;s arrangement flags.
Definition: layer.h:109
double alpha
Definition: pointset.h:53
static int reorderPolygon(int nbPoints, double *x, double *y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside...
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
Definition: feature.cpp:53
QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const
Returns the priority ordered list of predefined positions for label candidates.
qreal height() const
#define M_PI
Definition: feature.cpp:48
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:769
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
int size() const
double xmax
Definition: pointset.h:174
int char_num
Definition: feature.h:74
iterator begin()
int size() const
qreal width() const
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:98
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1732
double x() const
Get the x value of the point.
Definition: qgspoint.h:185
bool mOwnsGeom
Definition: pointset.h:150
void append(const T &value)
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:212
int connectedFeatureId(QgsFeatureId featureId) const
Returns the connected feature ID for a label feature ID, which is unique for all features which have ...
Definition: layer.cpp:414
int createCurvedCandidatesAlongLine(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:1143
Arranges candidates scattered throughout a polygon feature.