QGIS API Documentation  2.14.11-Essen
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 <QLinkedList>
43 #include <cmath>
44 #include <cfloat>
45 
46 #ifndef M_PI
47 #define M_PI 3.14159265358979323846
48 #endif
49 
50 using namespace pal;
51 
52 FeaturePart::FeaturePart( QgsLabelFeature* feat, const GEOSGeometry* geom )
53  : mLF( feat )
54 {
55  // we'll remove const, but we won't modify that geometry
56  mGeos = const_cast<GEOSGeometry*>( geom );
57  mOwnsGeom = false; // geometry is owned by Feature class
58 
59  extractCoords( geom );
60 
61  holeOf = nullptr;
62  for ( int i = 0; i < mHoles.count(); i++ )
63  {
64  mHoles.at( i )->holeOf = this;
65  }
66 
67 }
68 
70  : PointSet( other )
71  , mLF( other.mLF )
72 {
73  Q_FOREACH ( const FeaturePart* hole, other.mHoles )
74  {
75  mHoles << new FeaturePart( *hole );
76  mHoles.last()->holeOf = this;
77  }
78 }
79 
81 {
82  // X and Y are deleted in PointSet
83 
84  qDeleteAll( mHoles );
85  mHoles.clear();
86 }
87 
88 void FeaturePart::extractCoords( const GEOSGeometry* geom )
89 {
90  const GEOSCoordSequence *coordSeq;
91  GEOSContextHandle_t geosctxt = geosContext();
92 
93  type = GEOSGeomTypeId_r( geosctxt, geom );
94 
95  if ( type == GEOS_POLYGON )
96  {
97  if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
98  {
99  int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
100 
101  for ( int i = 0; i < numHoles; ++i )
102  {
103  const GEOSGeometry* interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
104  FeaturePart* hole = new FeaturePart( mLF, interior );
105  hole->holeOf = nullptr;
106  // possibly not needed. it's not done for the exterior ring, so I'm not sure
107  // why it's just done here...
108  GeomFunction::reorderPolygon( hole->nbPoints, hole->x, hole->y );
109 
110  mHoles << hole;
111  }
112  }
113 
114  // use exterior ring for the extraction of coordinates that follows
115  geom = GEOSGetExteriorRing_r( geosctxt, geom );
116  }
117  else
118  {
119  qDeleteAll( mHoles );
120  mHoles.clear();
121  }
122 
123  // find out number of points
124  nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
125  coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
126 
127  // initialize bounding box
128  xmin = ymin = DBL_MAX;
129  xmax = ymax = -DBL_MAX;
130 
131  // initialize coordinate arrays
132  deleteCoords();
133  x = new double[nbPoints];
134  y = new double[nbPoints];
135 
136  for ( int i = 0; i < nbPoints; ++i )
137  {
138  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] );
139  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] );
140 
141  xmax = x[i] > xmax ? x[i] : xmax;
142  xmin = x[i] < xmin ? x[i] : xmin;
143 
144  ymax = y[i] > ymax ? y[i] : ymax;
145  ymin = y[i] < ymin ? y[i] : ymin;
146  }
147 }
148 
150 {
151  return mLF->layer();
152 }
153 
155 {
156  return mLF->id();
157 }
158 
160 {
161  if ( !part )
162  return false;
163 
164  if ( mLF->layer()->name() != part->layer()->name() )
165  return false;
166 
167  if ( mLF->id() == part->featureId() )
168  return true;
169 
170  // any part of joined features are also treated as having the same label feature
171  int connectedFeatureId = mLF->layer()->connectedFeatureId( mLF->id() );
172  if ( connectedFeatureId >= 0 && connectedFeatureId == mLF->layer()->connectedFeatureId( part->featureId() ) )
173  return true;
174 
175  return false;
176 }
177 
178 LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
179 {
180  QPointF quadOffset = mLF->quadOffset();
181  qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
182 
183  if ( quadOffsetX < 0 )
184  {
185  if ( quadOffsetY < 0 )
186  {
188  }
189  else if ( quadOffsetY > 0 )
190  {
192  }
193  else
194  {
196  }
197  }
198  else if ( quadOffsetX > 0 )
199  {
200  if ( quadOffsetY < 0 )
201  {
203  }
204  else if ( quadOffsetY > 0 )
205  {
207  }
208  else
209  {
211  }
212  }
213  else
214  {
215  if ( quadOffsetY < 0 )
216  {
218  }
219  else if ( quadOffsetY > 0 )
220  {
222  }
223  else
224  {
226  }
227  }
228 }
229 
231 {
232  int nbp = 1;
233 
234  // get from feature
235  double labelW = getLabelWidth();
236  double labelH = getLabelHeight();
237 
238  double cost = 0.0001;
239  int id = 0;
240 
241  double xdiff = -labelW / 2.0;
242  double ydiff = -labelH / 2.0;
243 
244  if ( !qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
245  {
246  xdiff += labelW / 2.0 * mLF->quadOffset().x();
247  }
248  if ( !qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
249  {
250  ydiff += labelH / 2.0 * mLF->quadOffset().y();
251  }
252 
253  if ( ! mLF->hasFixedPosition() )
254  {
255  if ( !qgsDoubleNear( angle, 0.0 ) )
256  {
257  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
258  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
259  xdiff = xd;
260  ydiff = yd;
261  }
262  }
263 
265  {
266  //if in "around point" placement mode, then we use the label distance to determine
267  //the label's offset
268  if ( qgsDoubleNear( mLF->quadOffset().x(), 0.0 ) )
269  {
270  ydiff += mLF->quadOffset().y() * mLF->distLabel();
271  }
272  else if ( qgsDoubleNear( mLF->quadOffset().y(), 0.0 ) )
273  {
274  xdiff += mLF->quadOffset().x() * mLF->distLabel();
275  }
276  else
277  {
278  xdiff += mLF->quadOffset().x() * M_SQRT1_2 * mLF->distLabel();
279  ydiff += mLF->quadOffset().y() * M_SQRT1_2 * mLF->distLabel();
280  }
281  }
282  else
283  {
284  if ( !qgsDoubleNear( mLF->positionOffset().x(), 0.0 ) )
285  {
286  xdiff += mLF->positionOffset().x();
287  }
288  if ( !qgsDoubleNear( mLF->positionOffset().y(), 0.0 ) )
289  {
290  ydiff += mLF->positionOffset().y();
291  }
292  }
293 
294  double lx = x + xdiff;
295  double ly = y + ydiff;
296 
297  if ( mLF->permissibleZonePrepared() )
298  {
299  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
300  {
301  return 0;
302  }
303  }
304 
305  lPos << new LabelPosition( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() );
306  return nbp;
307 }
308 
310 {
312  double labelWidth = getLabelWidth();
313  double labelHeight = getLabelHeight();
314  double distanceToLabel = getLabelDistance();
315  const QgsLabelFeature::VisualMargin& visualMargin = mLF->visualMargin();
316 
317  double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
318  double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );
319 
320  double cost = 0.0001;
321  int i = 0;
322  Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
323  {
324  double alpha = 0.0;
325  double deltaX = 0;
326  double deltaY = 0;
328  switch ( position )
329  {
332  alpha = 3 * M_PI_4;
333  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
334  deltaY = -visualMargin.bottom + symbolHeightOffset;
335  break;
336 
338  quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
339  alpha = M_PI_2;
340  deltaX = -labelWidth / 4.0 - visualMargin.left;
341  deltaY = -visualMargin.bottom + symbolHeightOffset;
342  break;
343 
345  quadrant = LabelPosition::QuadrantAbove;
346  alpha = M_PI_2;
347  deltaX = -labelWidth / 2.0;
348  deltaY = -visualMargin.bottom + symbolHeightOffset;
349  break;
350 
352  quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
353  alpha = M_PI_2;
354  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
355  deltaY = -visualMargin.bottom + symbolHeightOffset;
356  break;
357 
360  alpha = M_PI_4;
361  deltaX = - visualMargin.left + symbolWidthOffset;
362  deltaY = -visualMargin.bottom + symbolHeightOffset;
363  break;
364 
366  quadrant = LabelPosition::QuadrantLeft;
367  alpha = M_PI;
368  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
369  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
370  break;
371 
373  quadrant = LabelPosition::QuadrantRight;
374  alpha = 0.0;
375  deltaX = -visualMargin.left + symbolWidthOffset;
376  deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
377  break;
378 
381  alpha = 5 * M_PI_4;
382  deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
383  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
384  break;
385 
387  quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
388  alpha = 3 * M_PI_2;
389  deltaX = -labelWidth / 4.0 - visualMargin.left;
390  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
391  break;
392 
394  quadrant = LabelPosition::QuadrantBelow;
395  alpha = 3 * M_PI_2;
396  deltaX = -labelWidth / 2.0;
397  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
398  break;
399 
401  quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
402  alpha = 3 * M_PI_2;
403  deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
404  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
405  break;
406 
409  alpha = 7 * M_PI_4;
410  deltaX = -visualMargin.left + symbolWidthOffset;
411  deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
412  break;
413  }
414 
415  //have bearing, distance - calculate reference point
416  double referenceX = cos( alpha ) * distanceToLabel + x;
417  double referenceY = sin( alpha ) * distanceToLabel + y;
418 
419  double labelX = referenceX + deltaX;
420  double labelY = referenceY + deltaY;
421 
422  if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
423  {
424  lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
425  //TODO - tweak
426  cost += 0.001;
427  }
428 
429  ++i;
430  }
431 
432  return lPos.count();
433 }
434 
436 {
437  double labelWidth = getLabelWidth();
438  double labelHeight = getLabelHeight();
439  double distanceToLabel = getLabelDistance();
440 
441  int numberCandidates = mLF->layer()->pal->point_p;
442 
443  int icost = 0;
444  int inc = 2;
445 
446  double candidateAngleIncrement = 2 * M_PI / numberCandidates; /* angle bw 2 pos */
447 
448  /* various angles */
449  double a90 = M_PI / 2;
450  double a180 = M_PI;
451  double a270 = a180 + a90;
452  double a360 = 2 * M_PI;
453 
454  double gamma1, gamma2;
455 
456  if ( distanceToLabel > 0 )
457  {
458  gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
459  gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
460  }
461  else
462  {
463  gamma1 = gamma2 = a90 / 3.0;
464  }
465 
466  if ( gamma1 > a90 / 3.0 )
467  gamma1 = a90 / 3.0;
468 
469  if ( gamma2 > a90 / 3.0 )
470  gamma2 = a90 / 3.0;
471 
472  QList< LabelPosition* > candidates;
473 
474  int i;
475  double angleToCandidate;
476  for ( i = 0, angleToCandidate = M_PI / 4; i < numberCandidates; i++, angleToCandidate += candidateAngleIncrement )
477  {
478  double labelX = x;
479  double labelY = y;
480 
481  if ( angleToCandidate > a360 )
482  angleToCandidate -= a360;
483 
485 
486  if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
487  {
488  labelX += distanceToLabel;
489  double iota = ( angleToCandidate + gamma1 );
490  if ( iota > a360 - gamma1 )
491  iota -= a360;
492 
493  //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
494  labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
495 
496  quadrant = LabelPosition::QuadrantRight;
497  }
498  else if ( angleToCandidate < a90 - gamma2 ) // top-right
499  {
500  labelX += distanceToLabel * cos( angleToCandidate );
501  labelY += distanceToLabel * sin( angleToCandidate );
503  }
504  else if ( angleToCandidate < a90 + gamma2 ) // top
505  {
506  //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
507  labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
508  labelY += distanceToLabel;
509  quadrant = LabelPosition::QuadrantAbove;
510  }
511  else if ( angleToCandidate < a180 - gamma1 ) // top left
512  {
513  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
514  labelY += distanceToLabel * sin( angleToCandidate );
516  }
517  else if ( angleToCandidate < a180 + gamma1 ) // left
518  {
519  labelX += -distanceToLabel - labelWidth;
520  //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
521  labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
522  quadrant = LabelPosition::QuadrantLeft;
523  }
524  else if ( angleToCandidate < a270 - gamma2 ) // down - left
525  {
526  labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
527  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
529  }
530  else if ( angleToCandidate < a270 + gamma2 ) // down
531  {
532  labelY += -distanceToLabel - labelHeight;
533  //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
534  labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
535  quadrant = LabelPosition::QuadrantBelow;
536  }
537  else if ( angleToCandidate < a360 ) // down - right
538  {
539  labelX += distanceToLabel * cos( angleToCandidate );
540  labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
542  }
543 
544  double cost;
545 
546  if ( numberCandidates == 1 )
547  cost = 0.0001;
548  else
549  cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
550 
551 
552  if ( mLF->permissibleZonePrepared() )
553  {
554  if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
555  {
556  continue;
557  }
558  }
559 
560  candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
561 
562  icost += inc;
563 
564  if ( icost == numberCandidates )
565  {
566  icost = numberCandidates - 1;
567  inc = -2;
568  }
569  else if ( icost > numberCandidates )
570  {
571  icost = numberCandidates - 2;
572  inc = -2;
573  }
574 
575  }
576 
577  if ( !candidates.isEmpty() )
578  {
579  for ( int i = 0; i < candidates.count(); ++i )
580  {
581  lPos << candidates.at( i );
582  }
583  }
584 
585  return candidates.count();
586 }
587 
588 // TODO work with squared distance by removing call to sqrt or dist_euc2d
590 {
591  int i;
592  double distlabel = getLabelDistance();
593 
594  double xrm = getLabelWidth();
595  double yrm = getLabelHeight();
596 
597  double *d; // segments lengths distance bw pt[i] && pt[i+1]
598  double *ad; // absolute distance bw pt[0] and pt[i] along the line
599  double ll; // line length
600  double dist;
601  double bx, by, ex, ey;
602  int nbls;
603  double alpha;
604  double cost;
605 
606  LineArrangementFlags flags = mLF->layer()->arrangementFlags();
607  if ( flags == 0 )
608  flags = FLAG_ON_LINE; // default flag
609 
610  QLinkedList<LabelPosition*> positions;
611 
612  int nbPoints;
613  double *x;
614  double *y;
615 
616  PointSet * line = mapShape;
617  nbPoints = line->nbPoints;
618  x = line->x;
619  y = line->y;
620 
621  d = new double[nbPoints-1];
622  ad = new double[nbPoints];
623 
624  ll = 0.0; // line length
625  for ( i = 0; i < line->nbPoints - 1; i++ )
626  {
627  if ( i == 0 )
628  ad[i] = 0;
629  else
630  ad[i] = ad[i-1] + d[i-1];
631 
632  d[i] = GeomFunction::dist_euc2d( x[i], y[i], x[i+1], y[i+1] );
633  ll += d[i];
634  }
635 
636  ad[line->nbPoints-1] = ll;
637 
638  nbls = static_cast< int >( ll / xrm ); // ratio bw line length and label width
639  dist = ( ll - xrm );
640  double l;
641 
642  if ( nbls > 0 )
643  {
644  //dist /= nbls;
645  l = 0;
646  dist = qMin( yrm, xrm );
647  }
648  else // line length < label with => centering label position
649  {
650  l = - ( xrm - ll ) / 2.0;
651  dist = xrm;
652  ll = xrm;
653  }
654 
655  double birdfly;
656  double beta;
657  i = 0;
658  while ( l < ll - xrm )
659  {
660  // => bx, by
661  line->getPointByDistance( d, ad, l, &bx, &by );
662  // same but l = l+xrm
663  line->getPointByDistance( d, ad, l + xrm, &ex, &ey );
664 
665  // Label is bigger than line ...
666  if ( l < 0 )
667  birdfly = sqrt(( x[nbPoints-1] - x[0] ) * ( x[nbPoints-1] - x[0] )
668  + ( y[nbPoints-1] - y[0] ) * ( y[nbPoints-1] - y[0] ) );
669  else
670  birdfly = sqrt(( ex - bx ) * ( ex - bx ) + ( ey - by ) * ( ey - by ) );
671 
672  cost = birdfly / xrm;
673  if ( cost > 0.98 )
674  cost = 0.0001;
675  else
676  cost = ( 1 - cost ) / 100; // < 0.0001, 0.01 > (but 0.005 is already pretty much)
677 
678  // penalize positions which are further from the line's midpoint
679  double costCenter = qAbs( ll / 2 - ( l + xrm / 2 ) ) / ll; // <0, 0.5>
680  cost += costCenter / 1000; // < 0, 0.0005 >
681 
682  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
683  {
684  alpha = 0.0;
685  }
686  else
687  alpha = atan2( ey - by, ex - bx );
688 
689  beta = alpha + M_PI / 2;
690 
692  {
693  // find out whether the line direction for this candidate is from right to left
694  bool isRightToLeft = ( alpha > M_PI / 2 || alpha <= -M_PI / 2 );
695  // meaning of above/below may be reversed if using line position dependent orientation
696  // and the line has right-to-left direction
697  bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
698  bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
699  bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
700 
701  if ( aboveLine )
702  {
703  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
704  positions.append( new LabelPosition( i, bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
705  }
706  if ( belowLine )
707  {
708  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
709  positions.append( new LabelPosition( i, bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
710  }
711  if ( flags & FLAG_ON_LINE )
712  {
713  if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
714  positions.append( new LabelPosition( i, bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
715  }
716  }
718  {
719  positions.append( new LabelPosition( i, bx - xrm / 2, by - yrm / 2, xrm, yrm, 0, cost, this ) ); // Line
720  }
721  else
722  {
723  // an invalid arrangement?
724  }
725 
726  l += dist;
727 
728  i++;
729 
730  if ( nbls == 0 )
731  break;
732  }
733 
734  //delete line;
735 
736  delete[] d;
737  delete[] ad;
738 
739  int nbp = positions.size();
740  while ( !positions.isEmpty() )
741  {
742  lPos << positions.takeFirst();
743  }
744 
745  return nbp;
746 }
747 
748 
749 LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
750 {
751  // Check that the given distance is on the given index and find the correct index and distance if not
752  while ( distance < 0 && index > 1 )
753  {
754  index--;
755  distance += path_distances[index];
756  }
757 
758  if ( index <= 1 && distance < 0 ) // We've gone off the start, fail out
759  {
760  return nullptr;
761  }
762 
763  // Same thing, checking if we go off the end
764  while ( index < path_positions->nbPoints && distance > path_distances[index] )
765  {
766  distance -= path_distances[index];
767  index += 1;
768  }
769  if ( index >= path_positions->nbPoints )
770  {
771  return nullptr;
772  }
773 
774  LabelInfo* li = mLF->curvedLabelInfo();
775 
776  // Keep track of the initial index,distance incase we need to re-call get_placement_offset
777  int initial_index = index;
778  double initial_distance = distance;
779 
780  double string_height = li->label_height;
781  double old_x = path_positions->x[index-1];
782  double old_y = path_positions->y[index-1];
783 
784  double new_x = path_positions->x[index];
785  double new_y = path_positions->y[index];
786 
787  double dx = new_x - old_x;
788  double dy = new_y - old_y;
789 
790  double segment_length = path_distances[index];
791  if ( qgsDoubleNear( segment_length, 0.0 ) )
792  {
793  // Not allowed to place across on 0 length segments or discontinuities
794  return nullptr;
795  }
796 
797  LabelPosition* slp = nullptr;
798  LabelPosition* slp_tmp = nullptr;
799  // current_placement = placement_result()
800  double angle = atan2( -dy, dx );
801 
802  bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
803  if ( !orientation_forced )
804  orientation = ( angle > 0.55 * M_PI || angle < -0.45 * M_PI ? -1 : 1 );
805 
806  int upside_down_char_count = 0; // Count of characters that are placed upside down.
807 
808  for ( int i = 0; i < li->char_num; i++ )
809  {
810  double last_character_angle = angle;
811 
812  // grab the next character according to the orientation
813  LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
814 
815  // Coordinates this character will start at
816  if ( qgsDoubleNear( segment_length, 0.0 ) )
817  {
818  // Not allowed to place across on 0 length segments or discontinuities
819  delete slp;
820  return nullptr;
821  }
822 
823  double start_x = old_x + dx * distance / segment_length;
824  double start_y = old_y + dy * distance / segment_length;
825  // Coordinates this character ends at, calculated below
826  double end_x = 0;
827  double end_y = 0;
828 
829  if ( segment_length - distance >= ci.width )
830  {
831  // if the distance remaining in this segment is enough, we just go further along the segment
832  distance += ci.width;
833  end_x = old_x + dx * distance / segment_length;
834  end_y = old_y + dy * distance / segment_length;
835  }
836  else
837  {
838  // If there isn't enough distance left on this segment
839  // then we need to search until we find the line segment that ends further than ci.width away
840  do
841  {
842  old_x = new_x;
843  old_y = new_y;
844  index++;
845  if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
846  {
847  delete slp;
848  return nullptr;
849  }
850  new_x = path_positions->x[index];
851  new_y = path_positions->y[index];
852  dx = new_x - old_x;
853  dy = new_y - old_y;
854  segment_length = path_distances[index];
855  }
856  while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_
857 
858  // Calculate the position to place the end of the character on
859  GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );
860 
861  // Need to calculate distance on the new segment
862  distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
863  }
864 
865  // Calculate angle from the start of the character to the end based on start_/end_ position
866  angle = atan2( start_y - end_y, end_x - start_x );
867  //angle = atan2(end_y-start_y, end_x-start_x);
868 
869  // Test last_character_angle vs angle
870  // since our rendering angle has changed then check against our
871  // max allowable angle change.
872  double angle_delta = last_character_angle - angle;
873  // normalise between -180 and 180
874  while ( angle_delta > M_PI ) angle_delta -= 2 * M_PI;
875  while ( angle_delta < -M_PI ) angle_delta += 2 * M_PI;
876  if (( li->max_char_angle_inside > 0 && angle_delta > 0
877  && angle_delta > li->max_char_angle_inside*( M_PI / 180 ) )
878  || ( li->max_char_angle_outside < 0 && angle_delta < 0
879  && angle_delta < li->max_char_angle_outside*( M_PI / 180 ) ) )
880  {
881  delete slp;
882  return nullptr;
883  }
884 
885  // Shift the character downwards since the draw position is specified at the baseline
886  // and we're calculating the mean line here
887  double dist = 0.9 * li->label_height / 2;
888  if ( orientation < 0 )
889  dist = -dist;
890  start_x += dist * cos( angle + M_PI_2 );
891  start_y -= dist * sin( angle + M_PI_2 );
892 
893  double render_angle = angle;
894 
895  double render_x = start_x;
896  double render_y = start_y;
897 
898  // Center the text on the line
899  //render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
900  //render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
901 
902  if ( orientation < 0 )
903  {
904  // rotate in place
905  render_x += ci.width * cos( render_angle ); //- (string_height-2)*sin(render_angle);
906  render_y -= ci.width * sin( render_angle ); //+ (string_height-2)*cos(render_angle);
907  render_angle += M_PI;
908  }
909 
910  LabelPosition* tmp = new LabelPosition( 0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this );
911  tmp->setPartId( orientation > 0 ? i : li->char_num - i - 1 );
912  if ( !slp )
913  slp = tmp;
914  else
915  slp_tmp->setNextPart( tmp );
916  slp_tmp = tmp;
917 
918  //current_placement.add_node(ci.character,render_x, -render_y, render_angle);
919  //current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
920 
921  // Normalise to 0 <= angle < 2PI
922  while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
923  while ( render_angle < 0 ) render_angle += 2 * M_PI;
924 
925  if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
926  upside_down_char_count++;
927  }
928  // END FOR
929 
930  // If we placed too many characters upside down
931  if ( upside_down_char_count >= li->char_num / 2.0 )
932  {
933  // if we auto-detected the orientation then retry with the opposite orientation
934  if ( !orientation_forced )
935  {
936  orientation = -orientation;
937  delete slp;
938  slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
939  }
940  else
941  {
942  // Otherwise we have failed to find a placement
943  delete slp;
944  return nullptr;
945  }
946  }
947 
948  return slp;
949 }
950 
951 static LabelPosition* _createCurvedCandidate( LabelPosition* lp, double angle, double dist )
952 {
953  LabelPosition* newLp = new LabelPosition( *lp );
954  newLp->offsetPosition( dist*cos( angle + M_PI / 2 ), dist*sin( angle + M_PI / 2 ) );
955  return newLp;
956 }
957 
959 {
960  LabelInfo* li = mLF->curvedLabelInfo();
961 
962  // label info must be present
963  if ( !li || li->char_num == 0 )
964  return 0;
965 
966  // distance calculation
967  double* path_distances = new double[mapShape->nbPoints];
968  double total_distance = 0;
969  double old_x = -1.0, old_y = -1.0;
970  for ( int i = 0; i < mapShape->nbPoints; i++ )
971  {
972  if ( i == 0 )
973  path_distances[i] = 0;
974  else
975  path_distances[i] = sqrt( pow( old_x - mapShape->x[i], 2 ) + pow( old_y - mapShape->y[i], 2 ) );
976  old_x = mapShape->x[i];
977  old_y = mapShape->y[i];
978 
979  total_distance += path_distances[i];
980  }
981 
982  if ( qgsDoubleNear( total_distance, 0.0 ) )
983  {
984  delete[] path_distances;
985  return 0;
986  }
987 
988  //calculate overall angle of line
989  double lineAngle;
990  double bx = mapShape->x[0];
991  double by = mapShape->y[0];
992  double ex = mapShape->x[ mapShape->nbPoints - 1 ];
993  double ey = mapShape->y[ mapShape->nbPoints - 1 ];
994  if ( qgsDoubleNear( ey, by ) && qgsDoubleNear( ex, bx ) )
995  {
996  lineAngle = 0.0;
997  }
998  else
999  lineAngle = atan2( ey - by, ex - bx );
1000 
1001  // find out whether the line direction for this candidate is from right to left
1002  bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
1003 
1004  QLinkedList<LabelPosition*> positions;
1005  double delta = qMax( li->label_height, total_distance / 10.0 );
1006 
1007  unsigned long flags = mLF->layer()->arrangementFlags();
1008  if ( flags == 0 )
1009  flags = FLAG_ON_LINE; // default flag
1010  // placements may need to be reversed if using line position dependent orientation
1011  // and the line has right-to-left direction
1012  bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1013 
1014  // generate curved labels
1015  for ( int i = 0; i*delta < total_distance; i++ )
1016  {
1017  LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, 0, 1, i * delta );
1018 
1019  if ( slp )
1020  {
1021  // evaluate cost
1022  double angle_diff = 0.0, angle_last = 0.0, diff;
1023  LabelPosition* tmp = slp;
1024  double sin_avg = 0, cos_avg = 0;
1025  while ( tmp )
1026  {
1027  if ( tmp != slp ) // not first?
1028  {
1029  diff = fabs( tmp->getAlpha() - angle_last );
1030  if ( diff > 2*M_PI ) diff -= 2 * M_PI;
1031  diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
1032  angle_diff += diff;
1033  }
1034 
1035  sin_avg += sin( tmp->getAlpha() );
1036  cos_avg += cos( tmp->getAlpha() );
1037  angle_last = tmp->getAlpha();
1038  tmp = tmp->getNextPart();
1039  }
1040 
1041  double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
1042  double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
1043  if ( cost < 0.0001 ) cost = 0.0001;
1044 
1045  // penalize positions which are further from the line's midpoint
1046  double labelCenter = ( i * delta ) + getLabelWidth() / 2;
1047  double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
1048  cost += costCenter / 1000; // < 0, 0.0005 >
1049  slp->setCost( cost );
1050 
1051  // average angle is calculated with respect to periodicity of angles
1052  double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1053  // displacement - we loop through 3 times, generating above, online then below line placements successively
1054  for ( int i = 0; i <= 2; ++i )
1055  {
1056  LabelPosition* p = nullptr;
1057  if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1058  p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1059  if ( i == 1 && flags & FLAG_ON_LINE )
1060  p = _createCurvedCandidate( slp, angle_avg, 0 );
1061  if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1062  p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1063 
1064  if ( p && mLF->permissibleZonePrepared() )
1065  {
1066  bool within = true;
1067  LabelPosition* currentPos = p;
1068  while ( within && currentPos )
1069  {
1070  within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1071  currentPos = currentPos->getNextPart();
1072  }
1073  if ( !within )
1074  {
1075  delete p;
1076  p = nullptr;
1077  }
1078  }
1079 
1080  if ( p )
1081  positions.append( p );
1082  }
1083  // delete original candidate
1084  delete slp;
1085  }
1086  }
1087 
1088 
1089  int nbp = positions.size();
1090  for ( int i = 0; i < nbp; i++ )
1091  {
1092  lPos << positions.takeFirst();
1093  }
1094 
1095  delete[] path_distances;
1096 
1097  return nbp;
1098 }
1099 
1100 
1101 
1102 
1103 /*
1104  * seg 2
1105  * pt3 ____________pt2
1106  * ¦ ¦
1107  * ¦ ¦
1108  * seg 3 ¦ BBOX ¦ seg 1
1109  * ¦ ¦
1110  * ¦____________¦
1111  * pt0 seg 0 pt1
1112  *
1113  */
1114 
1116 {
1117  int i;
1118  int j;
1119 
1120  double labelWidth = getLabelWidth();
1121  double labelHeight = getLabelHeight();
1122 
1123  QLinkedList<PointSet*> shapes_toProcess;
1124  QLinkedList<PointSet*> shapes_final;
1125 
1126  mapShape->parent = nullptr;
1127 
1128  shapes_toProcess.append( mapShape );
1129 
1130  splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
1131 
1132  int nbp;
1133 
1134  if ( !shapes_final.isEmpty() )
1135  {
1136  QLinkedList<LabelPosition*> positions;
1137 
1138  int id = 0; // ids for candidates
1139  double dlx, dly; // delta from label center and bottom-left corner
1140  double alpha = 0.0; // rotation for the label
1141  double px, py;
1142  double dx;
1143  double dy;
1144  int bbid;
1145  double beta;
1146  double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
1147  double rx, ry;
1148  CHullBox **boxes = new CHullBox*[shapes_final.size()];
1149  j = 0;
1150 
1151  // Compute bounding box foreach finalShape
1152  while ( !shapes_final.isEmpty() )
1153  {
1154  PointSet *shape = shapes_final.takeFirst();
1155  boxes[j] = shape->compute_chull_bbox();
1156 
1157  if ( shape->parent )
1158  delete shape;
1159 
1160  j++;
1161  }
1162 
1163  //dx = dy = min( yrm, xrm ) / 2;
1164  dx = labelWidth / 2.0;
1165  dy = labelHeight / 2.0;
1166 
1167 
1168  int numTry = 0;
1169 
1170  //fit in polygon only mode slows down calculation a lot, so if it's enabled
1171  //then use a smaller limit for number of iterations
1172  int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
1173 
1174  do
1175  {
1176  for ( bbid = 0; bbid < j; bbid++ )
1177  {
1178  CHullBox *box = boxes[bbid];
1179 
1180  if (( box->length * box->width ) > ( xmax - xmin ) *( ymax - ymin ) *5 )
1181  {
1182  // Very Large BBOX (should never occur)
1183  continue;
1184  }
1185 
1187  {
1188  //check width/height of bbox is sufficient for label
1189  if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1190  mLF->permissibleZone().boundingBox().height() < labelHeight )
1191  {
1192  //no way label can fit in this box, skip it
1193  continue;
1194  }
1195  }
1196 
1197  bool enoughPlace = false;
1199  {
1200  enoughPlace = true;
1201  px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1202  py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
1203  int i, j;
1204 
1205  // Virtual label: center on bbox center, label size = 2x original size
1206  // alpha = 0.
1207  // If all corner are in bbox then place candidates horizontaly
1208  for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
1209  {
1210  for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
1211  {
1212  if ( !mapShape->containsPoint( rx, ry ) )
1213  {
1214  enoughPlace = false;
1215  break;
1216  }
1217  }
1218  if ( !enoughPlace )
1219  {
1220  break;
1221  }
1222  }
1223 
1224  } // arrangement== FREE ?
1225 
1226  if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal || enoughPlace )
1227  {
1228  alpha = 0.0; // HORIZ
1229  }
1230  else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
1231  {
1232  if ( box->alpha <= M_PI / 4 )
1233  {
1234  alpha = box->alpha;
1235  }
1236  else
1237  {
1238  alpha = box->alpha - M_PI / 2;
1239  }
1240  }
1241  else if ( box->length > box->width )
1242  {
1243  alpha = box->alpha - M_PI / 2;
1244  }
1245  else
1246  {
1247  alpha = box->alpha;
1248  }
1249 
1250  beta = atan2( labelHeight, labelWidth ) + alpha;
1251 
1252 
1253  //alpha = box->alpha;
1254 
1255  // delta from label center and down-left corner
1256  dlx = cos( beta ) * diago;
1257  dly = sin( beta ) * diago;
1258 
1259  double px0, py0;
1260 
1261  px0 = box->width / 2.0;
1262  py0 = box->length / 2.0;
1263 
1264  px0 -= ceil( px0 / dx ) * dx;
1265  py0 -= ceil( py0 / dy ) * dy;
1266 
1267  for ( px = px0; px <= box->width; px += dx )
1268  {
1269  for ( py = py0; py <= box->length; py += dy )
1270  {
1271 
1272  rx = cos( box->alpha ) * px + cos( box->alpha - M_PI / 2 ) * py;
1273  ry = sin( box->alpha ) * px + sin( box->alpha - M_PI / 2 ) * py;
1274 
1275  rx += box->x[0];
1276  ry += box->y[0];
1277 
1278  bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1279  ? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1280  : mapShape->containsPoint( rx, ry ) );
1281  if ( candidateAcceptable )
1282  {
1283  // cost is set to minimal value, evaluated later
1284  positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
1285  }
1286  }
1287  }
1288  } // forall box
1289 
1290  nbp = positions.size();
1291  if ( nbp == 0 )
1292  {
1293  dx /= 2;
1294  dy /= 2;
1295  numTry++;
1296  }
1297  }
1298  while ( nbp == 0 && numTry < maxTry );
1299 
1300  nbp = positions.size();
1301 
1302  for ( i = 0; i < nbp; i++ )
1303  {
1304  lPos << positions.takeFirst();
1305  }
1306 
1307  for ( bbid = 0; bbid < j; bbid++ )
1308  {
1309  delete boxes[bbid];
1310  }
1311 
1312  delete[] boxes;
1313  }
1314  else
1315  {
1316  nbp = 0;
1317  }
1318 
1319  return nbp;
1320 }
1321 
1323  double bboxMin[2], double bboxMax[2],
1324  PointSet *mapShape, RTree<LabelPosition*, double, 2, double>* candidates )
1325 {
1326  double bbox[4];
1327 
1328  bbox[0] = bboxMin[0];
1329  bbox[1] = bboxMin[1];
1330  bbox[2] = bboxMax[0];
1331  bbox[3] = bboxMax[1];
1332 
1333  double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
1334 
1335  if ( mLF->hasFixedPosition() )
1336  {
1337  lPos << new LabelPosition( 0, mLF->fixedPosition().x(), mLF->fixedPosition().y(), getLabelWidth(), getLabelHeight(), angle, 0.0, this );
1338  }
1339  else
1340  {
1341  switch ( type )
1342  {
1343  case GEOS_POINT:
1345  createCandidatesOverPoint( x[0], y[0], lPos, angle );
1347  createCandidatesAtOrderedPositionsOverPoint( x[0], y[0], lPos, angle );
1348  else
1349  createCandidatesAroundPoint( x[0], y[0], lPos, angle );
1350  break;
1351  case GEOS_LINESTRING:
1353  createCurvedCandidatesAlongLine( lPos, mapShape );
1354  else
1355  createCandidatesAlongLine( lPos, mapShape );
1356  break;
1357 
1358  case GEOS_POLYGON:
1359  switch ( mLF->layer()->arrangement() )
1360  {
1363  double cx, cy;
1364  mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
1366  createCandidatesOverPoint( cx, cy, lPos, angle );
1367  else
1368  createCandidatesAroundPoint( cx, cy, lPos, angle );
1369  break;
1371  createCandidatesAlongLine( lPos, mapShape );
1372  break;
1373  default:
1374  createCandidatesForPolygon( lPos, mapShape );
1375  break;
1376  }
1377  }
1378  }
1379 
1380  // purge candidates that are outside the bbox
1381 
1383  while ( i.hasNext() )
1384  {
1385  LabelPosition* pos = i.next();
1386  bool outside = false;
1387  if ( mLF->layer()->pal->getShowPartial() )
1388  outside = !pos->isIntersect( bbox );
1389  else
1390  outside = !pos->isInside( bbox );
1391  if ( outside )
1392  {
1393  i.remove();
1394  delete pos;
1395  }
1396  else // this one is OK
1397  {
1398  pos->insertIntoIndex( candidates );
1399  }
1400  }
1401 
1402  qSort( lPos.begin(), lPos.end(), CostCalculator::candidateSortGrow );
1403  return lPos.count();
1404 }
1405 
1406 void FeaturePart::addSizePenalty( int nbp, QList< LabelPosition* >& lPos, double bbx[4], double bby[4] )
1407 {
1408  if ( !mGeos )
1409  createGeosGeom();
1410 
1411  GEOSContextHandle_t ctxt = geosContext();
1412  int geomType = GEOSGeomTypeId_r( ctxt, mGeos );
1413 
1414  double sizeCost = 0;
1415  if ( geomType == GEOS_LINESTRING )
1416  {
1417  double length;
1418  try
1419  {
1420  if ( GEOSLength_r( ctxt, mGeos, &length ) != 1 )
1421  return; // failed to calculate length
1422  }
1423  catch ( GEOSException &e )
1424  {
1425  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1426  return;
1427  }
1428  double bbox_length = qMax( bbx[2] - bbx[0], bby[2] - bby[0] );
1429  if ( length >= bbox_length / 4 )
1430  return; // the line is longer than quarter of height or width - don't penalize it
1431 
1432  sizeCost = 1 - ( length / ( bbox_length / 4 ) ); // < 0,1 >
1433  }
1434  else if ( geomType == GEOS_POLYGON )
1435  {
1436  double area;
1437  try
1438  {
1439  if ( GEOSArea_r( ctxt, mGeos, &area ) != 1 )
1440  return;
1441  }
1442  catch ( GEOSException &e )
1443  {
1444  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1445  return;
1446  }
1447  double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
1448  if ( area >= bbox_area / 16 )
1449  return; // covers more than 1/16 of our view - don't penalize it
1450 
1451  sizeCost = 1 - ( area / ( bbox_area / 16 ) ); // < 0, 1 >
1452  }
1453  else
1454  return; // no size penalty for points
1455 
1456  // apply the penalty
1457  for ( int i = 0; i < nbp; i++ )
1458  {
1459  lPos.at( i )->setCost( lPos.at( i )->cost() + sizeCost / 100 );
1460  }
1461 }
1462 
1464 {
1465  if ( !p2->mGeos )
1466  p2->createGeosGeom();
1467 
1468  try
1469  {
1470  return ( GEOSPreparedTouches_r( geosContext(), preparedGeom(), p2->mGeos ) == 1 );
1471  }
1472  catch ( GEOSException &e )
1473  {
1474  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1475  return false;
1476  }
1477 }
1478 
1480 {
1481  if ( !mGeos )
1482  createGeosGeom();
1483  if ( !other->mGeos )
1484  other->createGeosGeom();
1485 
1486  GEOSContextHandle_t ctxt = geosContext();
1487  try
1488  {
1489  GEOSGeometry* g1 = GEOSGeom_clone_r( ctxt, mGeos );
1490  GEOSGeometry* g2 = GEOSGeom_clone_r( ctxt, other->mGeos );
1491  GEOSGeometry* geoms[2] = { g1, g2 };
1492  GEOSGeometry* g = GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 );
1493  GEOSGeometry* gTmp = GEOSLineMerge_r( ctxt, g );
1494  GEOSGeom_destroy_r( ctxt, g );
1495 
1496  if ( GEOSGeomTypeId_r( ctxt, gTmp ) != GEOS_LINESTRING )
1497  {
1498  // sometimes it's not possible to merge lines (e.g. they don't touch at endpoints)
1499  GEOSGeom_destroy_r( ctxt, gTmp );
1500  return false;
1501  }
1502  invalidateGeos();
1503 
1504  // set up new geometry
1505  mGeos = gTmp;
1506  mOwnsGeom = true;
1507 
1508  deleteCoords();
1509  qDeleteAll( mHoles );
1510  mHoles.clear();
1511  extractCoords( mGeos );
1512  return true;
1513  }
1514  catch ( GEOSException &e )
1515  {
1516  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1517  return false;
1518  }
1519 }
1520 
1522 {
1523  if ( mLF->alwaysShow() )
1524  {
1525  //if feature is set to always show, bump the priority up by orders of magnitude
1526  //so that other feature's labels are unlikely to be placed over the label for this feature
1527  //(negative numbers due to how pal::extract calculates inactive cost)
1528  return -0.2;
1529  }
1530 
1531  return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
1532 }
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:1322
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:211
double length
Definition: pointset.h:56
static unsigned index
Label on bottom-left of point.
virtual ~FeaturePart()
Delete the feature.
Definition: feature.cpp:80
int createCandidatesForPolygon(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate candidates for polygon features.
Definition: feature.cpp:1115
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:154
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:232
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:55
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
void createGeosGeom() const
Definition: pointset.cpp:150
Candidates are placed in predefined positions around a point.
friend class LabelPosition
Definition: pointset.h:66
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:1479
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1463
QgsPoint fixedPosition() const
Coordinates of the fixed position (relevant only if hasFixedPosition() returns true) ...
double getLabelDistance() const
Definition: feature.h:197
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:309
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Definition: qgis.h:285
#define M_PI_2
Definition: util.cpp:43
double y() const
Get the y value of the point.
Definition: qgspoint.h:136
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:435
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:164
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 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:1406
PointSet * parent
Definition: pointset.h:161
double * x
Definition: pointset.h:152
double ymax
Definition: pointset.h:175
double xmin
Definition: pointset.h:172
double getHeight() const
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:207
bool isEmpty() const
PointSet * holeOf
Definition: pointset.h:160
static LabelPosition * _createCurvedCandidate(LabelPosition *lp, double angle, double dist)
Definition: feature.cpp:951
double ymin
Definition: pointset.h:174
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:149
double calculatePriority() const
Calculates the priority for the feature.
Definition: feature.cpp:1521
QgsPalLayerSettings::Placement arrangement() const
Returns the layer&#39;s arrangement policy.
Definition: layer.h:91
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:60
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:196
Stores visual margins for labels (left, right, top and bottom)
void deleteCoords()
Definition: pointset.cpp:232
Main class to handle feature.
Definition: feature.h:90
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()
int createCandidatesOverPoint(double x, double y, QList< LabelPosition *> &lPos, double angle)
Generate one candidate over or offset the specified point.
Definition: feature.cpp:230
double * y
Definition: pointset.h:153
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:245
void setNextPart(LabelPosition *next)
CHullBox * compute_chull_bbox()
Definition: pointset.cpp:572
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:159
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
double max_char_angle_inside
Definition: feature.h:71
double right
Right margin.
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
Label below point, slightly left of center.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
Definition: feature.cpp:88
QString name() const
Returns the layer&#39;s name.
Definition: layer.h:86
double getLabelWidth() const
Definition: feature.h:195
GEOSGeometry * mGeos
Definition: pointset.h:148
LabelPosition is a candidate feature label position.
Definition: labelposition.h:50
QgsLabelFeature * mLF
Definition: feature.h:231
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:60
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:102
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:52
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:47
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
double xmax
Definition: pointset.h:173
LabelPosition * curvedPlacementAtOffset(PointSet *path_positions, double *path_distances, int orientation, int index, double distance)
Definition: feature.cpp:749
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:128
bool mOwnsGeom
Definition: pointset.h:149
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:413
int createCurvedCandidatesAlongLine(QList< LabelPosition *> &lPos, PointSet *mapShape)
Generate curved candidates for line features.
Definition: feature.cpp:958
Arranges candidates scattered throughout a polygon feature.