QGIS API Documentation  2.17.0-Master (dfeb663)
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 line position dependent orientation
777  // and the line has right-to-left direction
778  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
779  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
780  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
781 
782  double placementCost = 0.0;
783  if ( belowLine )
784  {
785  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
786  {
787  lPos.append( new LabelPosition( i, candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
788  placementCost += 0.001;
789  }
790  }
791  if ( aboveLine )
792  {
793  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
794  {
795  lPos.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
796  placementCost += 0.001;
797  }
798  }
799  if ( flags & FLAG_ON_LINE )
800  {
801  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle ) )
802  lPos.append( new LabelPosition( i, candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line
803  }
804  }
806  {
807  lPos.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
808  }
809  else
810  {
811  // an invalid arrangement?
812  }
813 
814  currentDistanceAlongLine += lineStepDistance;
815  }
816  }
817 
818  delete[] segmentLengths;
819  delete[] distanceToSegment;
820  return lPos.size();
821 }
822 
824 {
825  double distanceLineToLabel = getLabelDistance();
826 
827  double labelWidth = getLabelWidth();
828  double labelHeight = getLabelHeight();
829 
830  double angle;
831  double cost;
832 
833  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
834  if ( flags == 0 )
835  flags = FLAG_ON_LINE; // default flag
836 
837  QList<LabelPosition*> positions;
838 
839  PointSet * line = mapShape;
840  int nbPoints = line->nbPoints;
841  double *x = line->x;
842  double *y = line->y;
843 
844  double* segmentLengths = new double[nbPoints-1]; // segments lengths distance bw pt[i] && pt[i+1]
845  double* distanceToSegment = new double[nbPoints]; // absolute distance bw pt[0] and pt[i] along the line
846 
847  double totalLineLength = 0.0; // line length
848  for ( int i = 0; i < line->nbPoints - 1; i++ )
849  {
850  if ( i == 0 )
851  distanceToSegment[i] = 0;
852  else
853  distanceToSegment[i] = distanceToSegment[i-1] + segmentLengths[i-1];
854 
855  segmentLengths[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
856  totalLineLength += segmentLengths[i];
857  }
858  distanceToSegment[line->nbPoints-1] = totalLineLength;
859 
860  double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
861  double currentDistanceAlongLine = 0;
862 
863  if ( totalLineLength > labelWidth )
864  {
865  lineStepDistance = qMin( qMin( labelHeight, labelWidth ), lineStepDistance / mLF->layer()->pal->line_p );
866  }
867  else // line length < label width => centering label position
868  {
869  currentDistanceAlongLine = - ( labelWidth - totalLineLength ) / 2.0;
870  lineStepDistance = -1;
871  totalLineLength = labelWidth;
872  }
873 
874  double candidateLength;
875  double beta;
876  double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
877  int i = 0;
878  while ( currentDistanceAlongLine < totalLineLength - labelWidth )
879  {
880  // calculate positions along linestring corresponding to start and end of current label candidate
881  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine, &candidateStartX, &candidateStartY );
882  line->getPointByDistance( segmentLengths, distanceToSegment, currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
883 
884  if ( currentDistanceAlongLine < 0 )
885  {
886  // label is bigger than line, use whole available line
887  candidateLength = sqrt(( x[nbPoints-1] - x[0] ) * ( x[nbPoints-1] - x[0] )
888  + ( y[nbPoints-1] - y[0] ) * ( y[nbPoints-1] - y[0] ) );
889  }
890  else
891  {
892  candidateLength = sqrt(( candidateEndX - candidateStartX ) * ( candidateEndX - candidateStartX ) + ( candidateEndY - candidateStartY ) * ( candidateEndY - candidateStartY ) );
893  }
894 
895  cost = candidateLength / labelWidth;
896  if ( cost > 0.98 )
897  cost = 0.0001;
898  else
899  {
900  // jaggy line has a greater cost
901  cost = ( 1 - cost ) / 100; // ranges from 0.0001 to 0.01 (however a cost 0.005 is already a lot!)
902  }
903 
904  // penalize positions which are further from the line's midpoint
905  double costCenter = qAbs( totalLineLength / 2 - ( currentDistanceAlongLine + labelWidth / 2 ) ) / totalLineLength; // <0, 0.5>
906  cost += costCenter / 1000; // < 0, 0.0005 >
907  cost += initialCost;
908 
909  if ( qgsDoubleNear( candidateEndY, candidateStartY ) && qgsDoubleNear( candidateEndX, candidateStartX ) )
910  {
911  angle = 0.0;
912  }
913  else
914  angle = atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
915 
916  beta = angle + M_PI / 2;
917 
919  {
920  // find out whether the line direction for this candidate is from right to left
921  bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
922  // meaning of above/below may be reversed if using line position dependent orientation
923  // and the line has right-to-left direction
924  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
925  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
926  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
927 
928  if ( aboveLine )
929  {
930  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) )
931  positions.append( new LabelPosition( i, candidateStartX + cos( beta ) *distanceLineToLabel, candidateStartY + sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
932  }
933  if ( belowLine )
934  {
935  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
936  positions.append( new LabelPosition( i, candidateStartX - cos( beta ) *( distanceLineToLabel + labelHeight ), candidateStartY - sin( beta ) *( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
937  }
938  if ( flags & FLAG_ON_LINE )
939  {
940  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle ) )
941  positions.append( new LabelPosition( i, candidateStartX - labelHeight*cos( beta ) / 2, candidateStartY - labelHeight*sin( beta ) / 2, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line
942  }
943  }
945  {
946  positions.append( new LabelPosition( i, candidateStartX - labelWidth / 2, candidateStartY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this ) ); // Line
947  }
948  else
949  {
950  // an invalid arrangement?
951  }
952 
953  currentDistanceAlongLine += lineStepDistance;
954 
955  i++;
956 
957  if ( lineStepDistance < 0 )
958  break;
959  }
960 
961  //delete line;
962 
963  delete[] segmentLengths;
964  delete[] distanceToSegment;
965 
966  lPos.append( positions );
967  return lPos.size();
968 }
969 
970 
971 LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
972 {
973  // Check that the given distance is on the given index and find the correct index and distance if not
974  while ( distance < 0 && index > 1 )
975  {
976  index--;
977  distance += path_distances[index];
978  }
979 
980  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
981  {
982  return nullptr;
983  }
984 
985  // Same thing, checking if we go off the end
986  while ( index < path_positions->nbPoints && distance > path_distances[index] )
987  {
988  distance -= path_distances[index];
989  index += 1;
990  }
991  if ( index >= path_positions->nbPoints )
992  {
993  return nullptr;
994  }
995 
996  LabelInfo* li = mLF->curvedLabelInfo();
997 
998  // Keep track of the initial index,distance incase we need to re-call get_placement_offset
999  int initial_index = index;
1000  double initial_distance = distance;
1001 
1002  double string_height = li->label_height;
1003  double old_x = path_positions->x[index-1];
1004  double old_y = path_positions->y[index-1];
1005 
1006  double new_x = path_positions->x[index];
1007  double new_y = path_positions->y[index];
1008 
1009  double dx = new_x - old_x;
1010  double dy = new_y - old_y;
1011 
1012  double segment_length = path_distances[index];
1013  if ( qgsDoubleNear( segment_length, 0.0 ) )
1014  {
1015  // Not allowed to place across on 0 length segments or discontinuities
1016  return nullptr;
1017  }
1018 
1019  LabelPosition* slp = nullptr;
1020  LabelPosition* slp_tmp = nullptr;
1021  // current_placement = placement_result()
1022  double angle = atan2( -dy, dx );
1023 
1024  bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
1025  if ( !orientation_forced )
1026  orientation = ( angle > 0.55 * M_PI || angle < -0.45 * M_PI ? -1 : 1 );
1027 
1028  int upside_down_char_count = 0; // Count of characters that are placed upside down.
1029 
1030  for ( int i = 0; i < li->char_num; i++ )
1031  {
1032  double last_character_angle = angle;
1033 
1034  // grab the next character according to the orientation
1035  LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
1036 
1037  // Coordinates this character will start at
1038  if ( qgsDoubleNear( segment_length, 0.0 ) )
1039  {
1040  // Not allowed to place across on 0 length segments or discontinuities
1041  delete slp;
1042  return nullptr;
1043  }
1044 
1045  double start_x = old_x + dx * distance / segment_length;
1046  double start_y = old_y + dy * distance / segment_length;
1047  // Coordinates this character ends at, calculated below
1048  double end_x = 0;
1049  double end_y = 0;
1050 
1051  if ( segment_length - distance >= ci.width )
1052  {
1053  // if the distance remaining in this segment is enough, we just go further along the segment
1054  distance += ci.width;
1055  end_x = old_x + dx * distance / segment_length;
1056  end_y = old_y + dy * distance / segment_length;
1057  }
1058  else
1059  {
1060  // If there isn't enough distance left on this segment
1061  // then we need to search until we find the line segment that ends further than ci.width away
1062  do
1063  {
1064  old_x = new_x;
1065  old_y = new_y;
1066  index++;
1067  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1068  {
1069  delete slp;
1070  return nullptr;
1071  }
1072  new_x = path_positions->x[index];
1073  new_y = path_positions->y[index];
1074  dx = new_x - old_x;
1075  dy = new_y - old_y;
1076  segment_length = path_distances[index];
1077  }
1078  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_
1079 
1080  // Calculate the position to place the end of the character on
1081  GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );
1082 
1083  // Need to calculate distance on the new segment
1084  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1085  }
1086 
1087  // Calculate angle from the start of the character to the end based on start_/end_ position
1088  angle = atan2( start_y - end_y, end_x - start_x );
1089  //angle = atan2(end_y-start_y, end_x-start_x);
1090 
1091  // Test last_character_angle vs angle
1092  // since our rendering angle has changed then check against our
1093  // max allowable angle change.
1094  double angle_delta = last_character_angle - angle;
1095  // normalise between -180 and 180
1096  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
1097  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
1098  if (( li->max_char_angle_inside > 0 && angle_delta > 0
1099  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
1100  || ( li->max_char_angle_outside < 0 && angle_delta < 0
1101  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
1102  {
1103  delete slp;
1104  return nullptr;
1105  }
1106 
1107  // Shift the character downwards since the draw position is specified at the baseline
1108  // and we're calculating the mean line here
1109  double dist = 0.9 * li->label_height / 2;
1110  if ( orientation < 0 )
1111  dist = -dist;
1112  start_x += dist * cos( angle + M_PI_2 );
1113  start_y -= dist * sin( angle + M_PI_2 );
1114 
1115  double render_angle = angle;
1116 
1117  double render_x = start_x;
1118  double render_y = start_y;
1119 
1120  // Center the text on the line
1121  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
1122  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
1123 
1124  if ( orientation < 0 )
1125  {
1126  // rotate in place
1127  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
1128  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
1129  render_angle += M_PI;
1130  }
1131 
1132  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
1133  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
1134  if ( !slp )
1135  slp = tmp;
1136  else
1137  slp_tmp->setNextPart( tmp );
1138  slp_tmp = tmp;
1139 
1140  //current_placement.add_node(ci.character,render_x, -render_y, render_angle);
1141  //current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
1142 
1143  // Normalise to 0 <= angle < 2PI
1144  while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
1145  while ( render_angle < 0 ) render_angle += 2 * M_PI;
1146 
1147  if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
1148  upside_down_char_count++;
1149  }
1150  // END FOR
1151 
1152  // If we placed too many characters upside down
1153  if ( upside_down_char_count >= li->char_num / 2.0 )
1154  {
1155  // if we auto-detected the orientation then retry with the opposite orientation
1156  if ( !orientation_forced )
1157  {
1158  orientation = -orientation;
1159  delete slp;
1160  slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
1161  }
1162  else
1163  {
1164  // Otherwise we have failed to find a placement
1165  delete slp;
1166  return nullptr;
1167  }
1168  }
1169 
1170  return slp;
1171 }
1172 
1173 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
1174 {
1175  LabelPosition* newLp = new LabelPosition( *lp );
1176  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
1177  return newLp;
1178 }
1179 
1181 {
1182  LabelInfo* li = mLF->curvedLabelInfo();
1183 
1184  // label info must be present
1185  if ( !li || li->char_num == 0 )
1186  return 0;
1187 
1188  // distance calculation
1189  double* path_distances = new double[mapShape->nbPoints];
1190  double total_distance = 0;
1191  double old_x = -1.0, old_y = -1.0;
1192  for ( int i = 0; i < mapShape->nbPoints; i++ )
1193  {
1194  if ( i == 0 )
1195  path_distances[i] = 0;
1196  else
1197  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
1198  old_x = mapShape->x[i];
1199  old_y = mapShape->y[i];
1200 
1201  total_distance += path_distances[i];
1202  }
1203 
1204  if ( qgsDoubleNear( total_distance, 0.0 ) )
1205  {
1206  delete[] path_distances;
1207  return 0;
1208  }
1209 
1210  //calculate overall angle of line
1211  double lineAngle;
1212  double bx = mapShape->x[0];
1213  double by = mapShape->y[0];
1214  double ex = mapShape->x[ mapShape->nbPoints - 1 ];
1215  double ey = mapShape->y[ mapShape->nbPoints - 1 ];
1216  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
1217  {
1218  lineAngle = 0.0;
1219  }
1220  else
1221  lineAngle = atan2( ey - by, ex - bx );
1222 
1223  // find out whether the line direction for this candidate is from right to left
1224  bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
1225 
1226  QLinkedList<LabelPosition*> positions;
1227  double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );
1228 
1229  unsigned long flags = mLF->layer()->arrangementFlags();
1230  if ( flags == 0 )
1231  flags = FLAG_ON_LINE; // default flag
1232  // placements may need to be reversed if using line position dependent orientation
1233  // and the line has right-to-left direction
1234  bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1235 
1236  // an orientation of 0 means try both orientations and choose the best
1237  int orientation = 0;
1238  if ( !( flags & FLAG_MAP_ORIENTATION )
1240  {
1241  //... but if we are labeling the perimeter of a polygon and using line orientation flags,
1242  // then we can only accept a single orientation, as we need to ensure that the labels fall
1243  // inside or outside the polygon (and not mixed)
1244  orientation = reversed ? -1 : 1;
1245  }
1246 
1247  // generate curved labels
1248  for ( int i = 0; i*delta < total_distance; i++ )
1249  {
1250  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta );
1251 
1252  if ( slp )
1253  {
1254  // evaluate cost
1255  double angle_diff = 0.0, angle_last = 0.0, diff;
1256  LabelPosition* tmp = slp;
1257  double sin_avg = 0, cos_avg = 0;
1258  while ( tmp )
1259  {
1260  if ( tmp != slp ) // not first?
1261  {
1262  diff = fabs( tmp->getAlpha() - angle_last );
1263  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1264  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1265  angle_diff += diff;
1266  }
1267 
1268  sin_avg += sin( tmp->getAlpha() );
1269  cos_avg += cos( tmp->getAlpha() );
1270  angle_last = tmp->getAlpha();
1271  tmp = tmp->getNextPart();
1272  }
1273 
1274  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1275  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1276  if ( cost < 0.0001 ) cost = 0.0001;
1277 
1278  // penalize positions which are further from the line's midpoint
1279  double labelCenter = ( i * delta ) + getLabelWidth() / 2;
1280  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1281  cost += costCenter / 1000; // < 0, 0.0005 >
1282  slp->setCost( cost );
1283 
1284  // average angle is calculated with respect to periodicity of angles
1285  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1286  // displacement - we loop through 3 times, generating above, online then below line placements successively
1287  for ( int i = 0; i <= 2; ++i )
1288  {
1289  LabelPosition* p = nullptr;
1290  if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1291  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1292  if ( i == 1 && flags & FLAG_ON_LINE )
1293  {
1294  p = _createCurvedCandidate( slp, angle_avg, 0 );
1295  p->setCost( p->cost() + 0.002 );
1296  }
1297  if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1298  {
1299  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1300  p->setCost( p->cost() + 0.001 );
1301  }
1302 
1303  if ( p && mLF->permissibleZonePrepared() )
1304  {
1305  bool within = true;
1306  LabelPosition* currentPos = p;
1307  while ( within && currentPos )
1308  {
1309  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1310  currentPos = currentPos->getNextPart();
1311  }
1312  if ( !within )
1313  {
1314  delete p;
1315  p = nullptr;
1316  }
1317  }
1318 
1319  if ( p )
1320  positions.append( p );
1321  }
1322  // delete original candidate
1323  delete slp;
1324  }
1325  }
1326 
1327 
1328  int nbp = positions.size();
1329  for ( int i = 0; i < nbp; i++ )
1330  {
1331  lPos << positions.takeFirst();
1332  }
1333 
1334  delete[] path_distances;
1335 
1336  return nbp;
1337 }
1338 
1339 
1340 
1341 
1342 /*
1343  * seg 2
1344  * pt3 ____________pt2
1345  * ¦ ¦
1346  * ¦ ¦
1347  * seg 3 ¦ BBOX ¦ seg 1
1348  * ¦ ¦
1349  * ¦____________¦
1350  * pt0 seg 0 pt1
1351  *
1352  */
1353 
1355 {
1356  int i;
1357  int j;
1358 
1359  double labelWidth = getLabelWidth();
1360  double labelHeight = getLabelHeight();
1361 
1362  QLinkedList<PointSet*> shapes_toProcess;
1363  QLinkedList<PointSet*> shapes_final;
1364 
1365  mapShape->parent = nullptr;
1366 
1367  shapes_toProcess.append( mapShape );
1368 
1369  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1370 
1371  int nbp;
1372 
1373  if ( !shapes_final.isEmpty() )
1374  {
1375  QLinkedList<LabelPosition*> positions;
1376 
1377  int id = 0; // ids for candidates
1378  double dlx, dly; // delta from label center and bottom-left corner
1379  double alpha = 0.0; // rotation for the label
1380  double px, py;
1381  double dx;
1382  double dy;
1383  int bbid;
1384  double beta;
1385  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1386  double rx, ry;
1387  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1388  j = 0;
1389 
1390  // Compute bounding box foreach finalShape
1391  while ( !shapes_final.isEmpty() )
1392  {
1393  PointSet *shape = shapes_final.takeFirst();
1394  boxes[j] = shape->compute_chull_bbox();
1395 
1396  if ( shape->parent )
1397  delete shape;
1398 
1399  j++;
1400  }
1401 
1402  //dx = dy = min( yrm, xrm ) / 2;
1403  dx = labelWidth / 2.0;
1404  dy = labelHeight / 2.0;
1405 
1406 
1407  int numTry = 0;
1408 
1409  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1410  //then use a smaller limit for number of iterations
1411  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1412 
1413  do
1414  {
1415  for ( bbid = 0; bbid < j; bbid++ )
1416  {
1417  CHullBox *box = boxes[bbid];
1418 
1419  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1420  {
1421  // Very Large BBOX (should never occur)
1422  continue;
1423  }
1424 
1426  {
1427  //check width/height of bbox is sufficient for label
1428  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1429  mLF->permissibleZone().boundingBox().height() < labelHeight )
1430  {
1431  //no way label can fit in this box, skip it
1432  continue;
1433  }
1434  }
1435 
1436  bool enoughPlace = false;
1438  {
1439  enoughPlace = true;
1440  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1441  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1442  int i, j;
1443 
1444  // Virtual label: center on bbox center, label size = 2x original size
1445  // alpha = 0.
1446  // If all corner are in bbox then place candidates horizontaly
1447  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1448  {
1449  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1450  {
1451  if ( !mapShape->containsPoint( rx, ry ) )
1452  {
1453  enoughPlace = false;
1454  break;
1455  }
1456  }
1457  if ( !enoughPlace )
1458  {
1459  break;
1460  }
1461  }
1462 
1463  } // arrangement== FREE ?
1464 
1465  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1466  {
1467  alpha = 0.0; // HORIZ
1468  }
1469  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1470  {
1471  if ( box->alpha <= M_PI / 4 )
1472  {
1473  alpha = box->alpha;
1474  }
1475  else
1476  {
1477  alpha = box->alpha - M_PI / 2;
1478  }
1479  }
1480  else if ( box->length > box->width )
1481  {
1482  alpha = box->alpha - M_PI / 2;
1483  }
1484  else
1485  {
1486  alpha = box->alpha;
1487  }
1488 
1489  beta = atan2( labelHeight, labelWidth ) + alpha;
1490 
1491 
1492  //alpha = box->alpha;
1493 
1494  // delta from label center and down-left corner
1495  dlx = cos( beta ) * diago;
1496  dly = sin( beta ) * diago;
1497 
1498  double px0, py0;
1499 
1500  px0 = box->width / 2.0;
1501  py0 = box->length / 2.0;
1502 
1503  px0 -= ceil( px0 / dx ) * dx;
1504  py0 -= ceil( py0 / dy ) * dy;
1505 
1506  for ( px = px0; px <= box->width; px += dx )
1507  {
1508  for ( py = py0; py <= box->length; py += dy )
1509  {
1510 
1511  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1512  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1513 
1514  rx += box->x[0];
1515  ry += box->y[0];
1516 
1517  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1518  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1519  : mapShape->containsPoint( rx, ry ) );
1520  if ( candidateAcceptable )
1521  {
1522  // cost is set to minimal value, evaluated later
1523  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1524  }
1525  }
1526  }
1527  } // forall box
1528 
1529  nbp = positions.size();
1530  if ( nbp == 0 )
1531  {
1532  dx /= 2;
1533  dy /= 2;
1534  numTry++;
1535  }
1536  }
1537  while ( nbp == 0 && numTry < maxTry );
1538 
1539  nbp = positions.size();
1540 
1541  for ( i = 0; i < nbp; i++ )
1542  {
1543  lPos << positions.takeFirst();
1544  }
1545 
1546  for ( bbid = 0; bbid < j; bbid++ )
1547  {
1548  delete boxes[bbid];
1549  }
1550 
1551  delete[] boxes;
1552  }
1553  else
1554  {
1555  nbp = 0;
1556  }
1557 
1558  return nbp;
1559 }
1560 
1562  double bboxMin[2], double bboxMax[2],
1563  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1564 {
1565  double bbox[4];
1566 
1567  bbox[0] = bboxMin[0];
1568  bbox[1] = bboxMin[1];
1569  bbox[2] = bboxMax[0];
1570  bbox[3] = bboxMax[1];
1571 
1572  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1573 
1574  if ( mLF->hasFixedPosition() )
1575  {
1576  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1577  }
1578  else
1579  {
1580  switch ( type )
1581  {
1582  case GEOS_POINT:
1584  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1586  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1587  else
1588  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1589  break;
1590  case GEOS_LINESTRING:
1592  createCurvedCandidatesAlongLine( lPos, mapShape );
1594  createCurvedCandidatesAlongLine( lPos, mapShape );
1595  else
1596  createCandidatesAlongLine( lPos, mapShape );
1597  break;
1598 
1599  case GEOS_POLYGON:
1600  switch ( mLF->layer()->arrangement() )
1601  {
1604  double cx, cy;
1605  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1607  createCandidatesOverPoint( cx, cy, lPos, angle );
1608  else
1609  createCandidatesAroundPoint( cx, cy, lPos, angle );
1610  break;
1612  createCandidatesAlongLine( lPos, mapShape );
1613  break;
1615  createCurvedCandidatesAlongLine( lPos, mapShape );
1616  break;
1617  default:
1618  createCandidatesForPolygon( lPos, mapShape );
1619  break;
1620  }
1621  }
1622  }
1623 
1624  // purge candidates that are outside the bbox
1625 
1627  while ( i.hasNext() )
1628  {
1629  LabelPosition* pos = i.next();
1630  bool outside = false;
1631  if ( mLF->layer()->pal->getShowPartial() )
1632  outside = !pos->isIntersect( bbox );
1633  else
1634  outside = !pos->isInside( bbox );
1635  if ( outside )
1636  {
1637  i.remove();
1638  delete pos;
1639  }
1640  else // this one is OK
1641  {
1642  pos->insertIntoIndex( candidates );
1643  }
1644  }
1645 
1646  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1647  return lPos.count();
1648 }
1649 
1650 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1651 {
1652  if ( !mGeos )
1653  createGeosGeom();
1654 
1655  GEOSContextHandle_t ctxt = geosContext();
1656  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1657 
1658  double sizeCost = 0;
1659  if ( geomType == GEOS_LINESTRING )
1660  {
1661  double length;
1662  try
1663  {
1664  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1665  return; // failed to calculate length
1666  }
1667  catch ( GEOSException &e )
1668  {
1669  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1670  return;
1671  }
1672  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1673  if ( length >= bbox_length / 4 )
1674  return; // the line is longer than quarter of height or width - don't penalize it
1675 
1676  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1677  }
1678  else if ( geomType == GEOS_POLYGON )
1679  {
1680  double area;
1681  try
1682  {
1683  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1684  return;
1685  }
1686  catch ( GEOSException &e )
1687  {
1688  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1689  return;
1690  }
1691  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1692  if ( area >= bbox_area / 16 )
1693  return; // covers more than 1/16 of our view - don't penalize it
1694 
1695  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1696  }
1697  else
1698  return; // no size penalty for points
1699 
1700  // apply the penalty
1701  for ( int i = 0; i < nbp; i++ )
1702  {
1703  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1704  }
1705 }
1706 
1708 {
1709  if ( !p2->mGeos )
1710  p2->createGeosGeom();
1711 
1712  try
1713  {
1714  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
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 ( !mGeos )
1726  createGeosGeom();
1727  if ( !other->mGeos )
1728  other->createGeosGeom();
1729 
1730  GEOSContextHandle_t ctxt = geosContext();
1731  try
1732  {
1733  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1734  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1735  GEOSGeometry* geoms[2] = { g1, g2 };
1736  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1737  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1738  GEOSGeom_destroy_r( ctxt, g );
1739 
1740  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1741  {
1742  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1743  GEOSGeom_destroy_r( ctxt, gTmp );
1744  return false;
1745  }
1746  invalidateGeos();
1747 
1748  // set up new geometry
1749  mGeos = gTmp;
1750  mOwnsGeom = true;
1751 
1752  deleteCoords();
1753  qDeleteAll( mHoles );
1754  mHoles.clear();
1755  extractCoords( mGeos );
1756  return true;
1757  }
1758  catch ( GEOSException &e )
1759  {
1760  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1761  return false;
1762  }
1763 }
1764 
1766 {
1767  if ( mLF->alwaysShow() )
1768  {
1769  //if feature is set to always show, bump the priority up by orders of magnitude
1770  //so that other feature's labels are unlikely to be placed over the label for this feature
1771  //(negative numbers due to how pal::extract calculates inactive cost)
1772  return -0.2;
1773  }
1774 
1775  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1776 }
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:1561
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:214
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:1354
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
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:251
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.
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:1723
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1707
QgsPoint fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
double getLabelDistance() const
Definition: feature.h:216
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:167
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 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:1650
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:1173
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:1765
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:215
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
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()
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:248
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:823
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:214
GEOSGeometry * mGeos
Definition: pointset.h:149
LabelPosition is a candidate feature label position.
Definition: labelposition.h:51
QgsLabelFeature * mLF
Definition: feature.h:250
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:105
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
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int orientation, int index, double distance)
Definition: feature.cpp:971
int char_num
Definition: feature.h:74
iterator begin()
int size() const
qreal width() const
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:1180
Arranges candidates scattered throughout a polygon feature.