QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
labelposition.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 "layer.h"
31 #include "pal.h"
32 #include "costcalculator.h"
33 #include "feature.h"
34 #include "geomfunction.h"
35 #include "labelposition.h"
36 #include "qgsgeos.h"
37 #include "qgsmessagelog.h"
38 #include <cmath>
39 #include <cfloat>
40 
41 using namespace pal;
42 
43 LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, bool isReversed, Quadrant quadrant )
44  : id( id )
45  , feature( feature )
46  , probFeat( 0 )
47  , nbOverlap( 0 )
48  , alpha( alpha )
49  , w( w )
50  , h( h )
51  , partId( -1 )
52  , reversed( isReversed )
53  , upsideDown( false )
54  , quadrant( quadrant )
55  , mCost( cost )
56  , mHasObstacleConflict( false )
57  , mUpsideDownCharCount( 0 )
58 {
59  type = GEOS_POLYGON;
60  nbPoints = 4;
61  x.resize( nbPoints );
62  y.resize( nbPoints );
63 
64  // alpha take his value bw 0 and 2*pi rad
65  while ( this->alpha > 2 * M_PI )
66  this->alpha -= 2 * M_PI;
67 
68  while ( this->alpha < 0 )
69  this->alpha += 2 * M_PI;
70 
71  double beta = this->alpha + M_PI_2;
72 
73  double dx1, dx2, dy1, dy2;
74 
75  dx1 = std::cos( this->alpha ) * w;
76  dy1 = std::sin( this->alpha ) * w;
77 
78  dx2 = std::cos( beta ) * h;
79  dy2 = std::sin( beta ) * h;
80 
81  x[0] = x1;
82  y[0] = y1;
83 
84  x[1] = x1 + dx1;
85  y[1] = y1 + dy1;
86 
87  x[2] = x1 + dx1 + dx2;
88  y[2] = y1 + dy1 + dy2;
89 
90  x[3] = x1 + dx2;
91  y[3] = y1 + dy2;
92 
93  // upside down ? (curved labels are always correct)
94  if ( !feature->layer()->isCurved() &&
95  this->alpha > M_PI_2 && this->alpha <= 3 * M_PI_2 )
96  {
97  if ( feature->showUprightLabels() )
98  {
99  // Turn label upsidedown by inverting boundary points
100  double tx, ty;
101 
102  tx = x[0];
103  ty = y[0];
104 
105  x[0] = x[2];
106  y[0] = y[2];
107 
108  x[2] = tx;
109  y[2] = ty;
110 
111  tx = x[1];
112  ty = y[1];
113 
114  x[1] = x[3];
115  y[1] = y[3];
116 
117  x[3] = tx;
118  y[3] = ty;
119 
120  if ( this->alpha < M_PI )
121  this->alpha += M_PI;
122  else
123  this->alpha -= M_PI;
124 
125  // labels with text shown upside down are not classified as upsideDown,
126  // only those whose boundary points have been inverted
127  upsideDown = true;
128  }
129  }
130 
131  for ( int i = 0; i < nbPoints; ++i )
132  {
133  xmin = std::min( xmin, x[i] );
134  xmax = std::max( xmax, x[i] );
135  ymin = std::min( ymin, y[i] );
136  ymax = std::max( ymax, y[i] );
137  }
138 }
139 
141  : PointSet( other )
142 {
143  id = other.id;
144  mCost = other.mCost;
145  feature = other.feature;
146  probFeat = other.probFeat;
147  nbOverlap = other.nbOverlap;
148 
149  alpha = other.alpha;
150  w = other.w;
151  h = other.h;
152 
153  if ( other.nextPart )
154  nextPart = new LabelPosition( *other.nextPart );
155  else
156  nextPart = nullptr;
157  partId = other.partId;
158  upsideDown = other.upsideDown;
159  reversed = other.reversed;
160  quadrant = other.quadrant;
161  mHasObstacleConflict = other.mHasObstacleConflict;
162  mUpsideDownCharCount = other.mUpsideDownCharCount;
163 }
164 
165 bool LabelPosition::isIn( double *bbox )
166 {
167  int i;
168 
169  for ( i = 0; i < 4; i++ )
170  {
171  if ( x[i] >= bbox[0] && x[i] <= bbox[2] &&
172  y[i] >= bbox[1] && y[i] <= bbox[3] )
173  return true;
174  }
175 
176  if ( nextPart )
177  return nextPart->isIn( bbox );
178  else
179  return false;
180 }
181 
182 bool LabelPosition::isIntersect( double *bbox )
183 {
184  int i;
185 
186  for ( i = 0; i < 4; i++ )
187  {
188  if ( x[i] >= bbox[0] && x[i] <= bbox[2] &&
189  y[i] >= bbox[1] && y[i] <= bbox[3] )
190  return true;
191  }
192 
193  if ( nextPart )
194  return nextPart->isIntersect( bbox );
195  else
196  return false;
197 }
198 
199 bool LabelPosition::intersects( const GEOSPreparedGeometry *geometry )
200 {
201  if ( !mGeos )
202  createGeosGeom();
203 
204  try
205  {
206  if ( GEOSPreparedIntersects_r( QgsGeos::getGEOSHandler(), geometry, mGeos ) == 1 )
207  {
208  return true;
209  }
210  else if ( nextPart )
211  {
212  return nextPart->intersects( geometry );
213  }
214  }
215  catch ( GEOSException &e )
216  {
217  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
218  return false;
219  }
220 
221  return false;
222 }
223 
224 bool LabelPosition::within( const GEOSPreparedGeometry *geometry )
225 {
226  if ( !mGeos )
227  createGeosGeom();
228 
229  try
230  {
231  if ( GEOSPreparedContains_r( QgsGeos::getGEOSHandler(), geometry, mGeos ) != 1 )
232  {
233  return false;
234  }
235  else if ( nextPart )
236  {
237  return nextPart->within( geometry );
238  }
239  }
240  catch ( GEOSException &e )
241  {
242  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
243  return false;
244  }
245 
246  return true;
247 }
248 
249 bool LabelPosition::isInside( double *bbox )
250 {
251  for ( int i = 0; i < 4; i++ )
252  {
253  if ( !( x[i] >= bbox[0] && x[i] <= bbox[2] &&
254  y[i] >= bbox[1] && y[i] <= bbox[3] ) )
255  return false;
256  }
257 
258  if ( nextPart )
259  return nextPart->isInside( bbox );
260  else
261  return true;
262 }
263 
265 {
266  if ( this->probFeat == lp->probFeat ) // bugfix #1
267  return false; // always overlaping itself !
268 
269  if ( !nextPart && !lp->nextPart )
270  return isInConflictSinglePart( lp );
271  else
272  return isInConflictMultiPart( lp );
273 }
274 
276 {
277  if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
278  {
279  // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
280  return boundingBoxIntersects( lp );
281  }
282 
283  if ( !mGeos )
284  createGeosGeom();
285 
286  if ( !lp->mGeos )
287  lp->createGeosGeom();
288 
289  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
290  try
291  {
292  bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedGeom(), lp->mGeos ) == 1 );
293  return result;
294  }
295  catch ( GEOSException &e )
296  {
297  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
298  return false;
299  }
300 }
301 
303 {
304  // check all parts against all parts of other one
305  LabelPosition *tmp1 = this;
306  while ( tmp1 )
307  {
308  // check tmp1 against parts of other label
309  LabelPosition *tmp2 = lp;
310  while ( tmp2 )
311  {
312  if ( tmp1->isInConflictSinglePart( tmp2 ) )
313  return true;
314  tmp2 = tmp2->nextPart;
315  }
316 
317  tmp1 = tmp1->nextPart;
318  }
319  return false; // no conflict found
320 }
321 
322 int LabelPosition::partCount() const
323 {
324  if ( nextPart )
325  return nextPart->partCount() + 1;
326  else
327  return 1;
328 }
329 
330 void LabelPosition::offsetPosition( double xOffset, double yOffset )
331 {
332  for ( int i = 0; i < 4; i++ )
333  {
334  x[i] += xOffset;
335  y[i] += yOffset;
336  }
337 
338  if ( nextPart )
339  nextPart->offsetPosition( xOffset, yOffset );
340 
341  invalidateGeos();
342 }
343 
345 {
346  return id;
347 }
348 
349 double LabelPosition::getX( int i ) const
350 {
351  return ( i >= 0 && i < 4 ? x[i] : -1 );
352 }
353 
354 double LabelPosition::getY( int i ) const
355 {
356  return ( i >= 0 && i < 4 ? y[i] : -1 );
357 }
358 
360 {
361  return alpha;
362 }
363 
365 {
366  if ( mCost >= 1 )
367  {
368  mCost -= int ( mCost ); // label cost up to 1
369  }
370 }
371 
373 {
374  return feature;
375 }
376 
377 void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
378 {
379  if ( nextPart )
380  {
381  nextPart->getBoundingBox( amin, amax );
382  }
383  else
384  {
385  amin[0] = std::numeric_limits<double>::max();
386  amax[0] = std::numeric_limits<double>::lowest();
387  amin[1] = std::numeric_limits<double>::max();
388  amax[1] = std::numeric_limits<double>::lowest();
389  }
390  for ( int c = 0; c < 4; c++ )
391  {
392  if ( x[c] < amin[0] )
393  amin[0] = x[c];
394  if ( x[c] > amax[0] )
395  amax[0] = x[c];
396  if ( y[c] < amin[1] )
397  amin[1] = y[c];
398  if ( y[c] > amax[1] )
399  amax[1] = y[c];
400  }
401 }
402 
404 {
405  mHasObstacleConflict = conflicts;
406  if ( nextPart )
407  nextPart->setConflictsWithObstacle( conflicts );
408 }
409 
411 {
412  PolygonCostCalculator *pCost = reinterpret_cast< PolygonCostCalculator * >( ctx );
413 
414  LabelPosition *lp = pCost->getLabel();
415  if ( ( obstacle == lp->feature ) || ( obstacle->getHoleOf() && obstacle->getHoleOf() != lp->feature ) )
416  {
417  return true;
418  }
419 
420  pCost->update( obstacle );
421 
422  return true;
423 }
424 
425 void LabelPosition::removeFromIndex( RTree<LabelPosition *, double, 2, double> *index )
426 {
427  double amin[2];
428  double amax[2];
429  getBoundingBox( amin, amax );
430  index->Remove( amin, amax, this );
431 }
432 
433 void LabelPosition::insertIntoIndex( RTree<LabelPosition *, double, 2, double> *index )
434 {
435  double amin[2];
436  double amax[2];
437  getBoundingBox( amin, amax );
438  index->Insert( amin, amax, this );
439 }
440 
441 bool LabelPosition::pruneCallback( LabelPosition *candidatePosition, void *ctx )
442 {
443  FeaturePart *obstaclePart = ( reinterpret_cast< PruneCtx * >( ctx ) )->obstacle;
444 
445  // test whether we should ignore this obstacle for the candidate. We do this if:
446  // 1. it's not a hole, and the obstacle belongs to the same label feature as the candidate (e.g.,
447  // features aren't obstacles for their own labels)
448  // 2. it IS a hole, and the hole belongs to a different label feature to the candidate (e.g., holes
449  // are ONLY obstacles for the labels of the feature they belong to)
450  if ( ( !obstaclePart->getHoleOf() && candidatePosition->feature->hasSameLabelFeatureAs( obstaclePart ) )
451  || ( obstaclePart->getHoleOf() && !candidatePosition->feature->hasSameLabelFeatureAs( dynamic_cast< FeaturePart * >( obstaclePart->getHoleOf() ) ) ) )
452  {
453  return true;
454  }
455 
456  CostCalculator::addObstacleCostPenalty( candidatePosition, obstaclePart );
457 
458  return true;
459 }
460 
462 {
463  LabelPosition *lp2 = reinterpret_cast< LabelPosition * >( ctx );
464 
465  if ( lp2->isInConflict( lp ) )
466  {
467  lp2->nbOverlap++;
468  }
469 
470  return true;
471 }
472 
474 {
475  CountContext *context = reinterpret_cast< CountContext * >( ctx );
476  LabelPosition *lp2 = context->lp;
477  double *cost = context->cost;
478  int *nbOv = context->nbOv;
479  double *inactiveCost = context->inactiveCost;
480  if ( lp2->isInConflict( lp ) )
481  {
482  ( *nbOv ) ++;
483  *cost += inactiveCost[lp->probFeat] + lp->cost();
484  }
485 
486  return true;
487 }
488 
490 {
491  LabelPosition *lp2 = reinterpret_cast< LabelPosition * >( ctx );
492 
493  if ( lp2->isInConflict( lp ) )
494  {
495  lp->nbOverlap--;
496  lp2->nbOverlap--;
497  }
498 
499  return true;
500 }
501 
502 double LabelPosition::getDistanceToPoint( double xp, double yp ) const
503 {
504  //first check if inside, if so then distance is -1
505  double distance = ( containsPoint( xp, yp ) ? -1
506  : std::sqrt( minDistanceToPoint( xp, yp ) ) );
507 
508  if ( nextPart && distance > 0 )
509  return std::min( distance, nextPart->getDistanceToPoint( xp, yp ) );
510 
511  return distance;
512 }
513 
515 {
516  if ( !mGeos )
517  createGeosGeom();
518 
519  if ( !line->mGeos )
520  line->createGeosGeom();
521 
522  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
523  try
524  {
525  if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mGeos ) == 1 )
526  {
527  return true;
528  }
529  else if ( nextPart )
530  {
531  return nextPart->crossesLine( line );
532  }
533  }
534  catch ( GEOSException &e )
535  {
536  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
537  return false;
538  }
539 
540  return false;
541 }
542 
544 {
545  if ( !mGeos )
546  createGeosGeom();
547 
548  if ( !polygon->mGeos )
549  polygon->createGeosGeom();
550 
551  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
552  try
553  {
554  if ( GEOSPreparedOverlaps_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1
555  || GEOSPreparedTouches_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
556  {
557  return true;
558  }
559  else if ( nextPart )
560  {
561  return nextPart->crossesBoundary( polygon );
562  }
563  }
564  catch ( GEOSException &e )
565  {
566  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
567  return false;
568  }
569 
570  return false;
571 }
572 
574 {
575  //effectively take the average polygon intersection cost for all label parts
576  double totalCost = polygonIntersectionCostForParts( polygon );
577  int n = partCount();
578  return std::ceil( totalCost / n );
579 }
580 
582 {
583  if ( !mGeos )
584  createGeosGeom();
585 
586  if ( !polygon->mGeos )
587  polygon->createGeosGeom();
588 
589  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
590  try
591  {
592  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
593  {
594  return true;
595  }
596  }
597  catch ( GEOSException &e )
598  {
599  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
600  }
601 
602  if ( nextPart )
603  {
604  return nextPart->intersectsWithPolygon( polygon );
605  }
606  else
607  {
608  return false;
609  }
610 }
611 
612 double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
613 {
614  if ( !mGeos )
615  createGeosGeom();
616 
617  if ( !polygon->mGeos )
618  polygon->createGeosGeom();
619 
620  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
621  double cost = 0;
622  try
623  {
624  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
625  {
626  //at least a partial intersection
627  cost += 1;
628 
629  double px, py;
630 
631  // check each corner
632  for ( int i = 0; i < 4; ++i )
633  {
634  px = x[i];
635  py = y[i];
636 
637  for ( int a = 0; a < 2; ++a ) // and each middle of segment
638  {
639  if ( polygon->containsPoint( px, py ) )
640  cost++;
641  px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
642  py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
643  }
644  }
645 
646  px = ( x[0] + x[2] ) / 2.0;
647  py = ( y[0] + y[2] ) / 2.0;
648 
649  //check the label center. if covered by polygon, cost of 4
650  if ( polygon->containsPoint( px, py ) )
651  cost += 4;
652  }
653  }
654  catch ( GEOSException &e )
655  {
656  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
657  }
658 
659  //maintain scaling from 0 -> 12
660  cost = 12.0 * cost / 13.0;
661 
662  if ( nextPart )
663  {
664  cost += nextPart->polygonIntersectionCostForParts( polygon );
665  }
666 
667  return cost;
668 }
bool isInConflict(LabelPosition *ls)
Check whether or not this overlap with another labelPosition.
FeaturePart * feature
PointSet * getHoleOf()
Returns nullptr if this isn&#39;t a hole. Otherwise returns pointer to parent pointset.
Definition: pointset.h:158
void invalidateGeos()
Definition: pointset.cpp:171
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:237
bool isInConflictMultiPart(LabelPosition *lp)
void getBoundingBox(double amin[2], double amax[2]) const
Returns bounding box - amin: xmin,ymin - amax: xmax,ymax.
bool intersectsWithPolygon(PointSet *polygon) const
Returns true if any intersection between polygon and position exists.
double getY(int i=0) const
Returns the down-left y coordinate.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
std::vector< double > x
Definition: pointset.h:188
void createGeosGeom() const
Definition: pointset.cpp:117
static bool countFullOverlapCallback(LabelPosition *lp, void *ctx)
FeaturePart * getFeaturePart()
Returns the feature corresponding to this labelposition.
static bool removeOverlapCallback(LabelPosition *lp, void *ctx)
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2825
bool boundingBoxIntersects(const PointSet *other) const
Returns true if the bounding box of this pointset intersects the bounding box of another pointset...
Definition: pointset.cpp:861
void update(pal::PointSet *pset)
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
static void addObstacleCostPenalty(LabelPosition *lp, pal::FeaturePart *obstacle)
Increase candidate&#39;s cost according to its collision with passed feature.
double cost() const
Returns the candidate label position&#39;s geographical cost.
LabelPosition * nextPart
double ymax
Definition: pointset.h:215
double xmin
Definition: pointset.h:212
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
double ymin
Definition: pointset.h:214
LabelPosition(int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, bool isReversed=false, Quadrant quadrant=QuadrantOver)
create a new LabelPosition
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:146
void insertIntoIndex(RTree< LabelPosition *, double, 2, double > *index)
void validateCost()
Make sure the cost is less than 1.
void removeFromIndex(RTree< LabelPosition *, double, 2, double > *index)
Main class to handle feature.
Definition: feature.h:96
static bool pruneCallback(LabelPosition *candidatePosition, void *ctx)
Check whether the candidate in ctx overlap with obstacle feat.
int getId() const
Returns the id.
int polygonIntersectionCost(PointSet *polygon) const
Returns cost of position intersection with polygon (testing area of intersection and center)...
void setConflictsWithObstacle(bool conflicts)
Sets whether the position is marked as conflicting with an obstacle feature.
static bool countOverlapCallback(LabelPosition *lp, void *ctx)
static bool polygonObstacleCallback(pal::FeaturePart *obstacle, void *ctx)
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part...
Definition: feature.cpp:156
double getAlpha() const
Returns the angle to rotate text (in rad).
double getX(int i=0) const
Returns the down-left x coordinate.
bool isIn(double *bbox)
Is the labelposition in the bounding-box ? (intersect or inside????)
GEOSGeometry * mGeos
Definition: pointset.h:192
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:65
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:159
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
bool isInConflictSinglePart(LabelPosition *lp)
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
bool crossesBoundary(PointSet *polygon) const
Returns true if this label crosses the boundary of the specified polygon.
double xmax
Definition: pointset.h:213
std::vector< double > y
Definition: pointset.h:189
Data structure to compute polygon&#39;s candidates costs.
double minDistanceToPoint(double px, double py, double *rx=nullptr, double *ry=nullptr) const
Returns the squared minimum distance between the point set geometry and the point (px...
Definition: pointset.cpp:775
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:169
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1820
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
LabelPosition::Quadrant quadrant
double getDistanceToPoint(double xp, double yp) const
Gets distance from this label to a point. If point lies inside, returns negative number.