QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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.mNextPart )
154  mNextPart = qgis::make_unique< LabelPosition >( *other.mNextPart );
155 
156  partId = other.partId;
157  upsideDown = other.upsideDown;
158  reversed = other.reversed;
159  quadrant = other.quadrant;
160  mHasObstacleConflict = other.mHasObstacleConflict;
161  mUpsideDownCharCount = other.mUpsideDownCharCount;
162 }
163 
164 bool LabelPosition::isIn( double *bbox )
165 {
166  int i;
167 
168  for ( i = 0; i < 4; i++ )
169  {
170  if ( x[i] >= bbox[0] && x[i] <= bbox[2] &&
171  y[i] >= bbox[1] && y[i] <= bbox[3] )
172  return true;
173  }
174 
175  if ( mNextPart )
176  return mNextPart->isIn( bbox );
177  else
178  return false;
179 }
180 
181 bool LabelPosition::isIntersect( double *bbox )
182 {
183  int i;
184 
185  for ( i = 0; i < 4; i++ )
186  {
187  if ( x[i] >= bbox[0] && x[i] <= bbox[2] &&
188  y[i] >= bbox[1] && y[i] <= bbox[3] )
189  return true;
190  }
191 
192  if ( mNextPart )
193  return mNextPart->isIntersect( bbox );
194  else
195  return false;
196 }
197 
198 bool LabelPosition::intersects( const GEOSPreparedGeometry *geometry )
199 {
200  if ( !mGeos )
201  createGeosGeom();
202 
203  try
204  {
205  if ( GEOSPreparedIntersects_r( QgsGeos::getGEOSHandler(), geometry, mGeos ) == 1 )
206  {
207  return true;
208  }
209  else if ( mNextPart )
210  {
211  return mNextPart->intersects( geometry );
212  }
213  }
214  catch ( GEOSException &e )
215  {
216  qWarning( "GEOS exception: %s", e.what() );
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 ( mNextPart )
236  {
237  return mNextPart->within( geometry );
238  }
239  }
240  catch ( GEOSException &e )
241  {
242  qWarning( "GEOS exception: %s", e.what() );
243  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
244  return false;
245  }
246 
247  return true;
248 }
249 
250 bool LabelPosition::isInside( double *bbox )
251 {
252  for ( int i = 0; i < 4; i++ )
253  {
254  if ( !( x[i] >= bbox[0] && x[i] <= bbox[2] &&
255  y[i] >= bbox[1] && y[i] <= bbox[3] ) )
256  return false;
257  }
258 
259  if ( mNextPart )
260  return mNextPart->isInside( bbox );
261  else
262  return true;
263 }
264 
266 {
267  if ( this->probFeat == lp->probFeat ) // bugfix #1
268  return false; // always overlaping itself !
269 
270  if ( !nextPart() && !lp->nextPart() )
271  return isInConflictSinglePart( lp );
272  else
273  return isInConflictMultiPart( lp );
274 }
275 
276 bool LabelPosition::isInConflictSinglePart( const LabelPosition *lp ) const
277 {
278  if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
279  {
280  // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
281  return boundingBoxIntersects( lp );
282  }
283 
284  if ( !mGeos )
285  createGeosGeom();
286 
287  if ( !lp->mGeos )
288  lp->createGeosGeom();
289 
290  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
291  try
292  {
293  bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedGeom(), lp->mGeos ) == 1 );
294  return result;
295  }
296  catch ( GEOSException &e )
297  {
298  qWarning( "GEOS exception: %s", e.what() );
299  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
300  return false;
301  }
302 }
303 
304 bool LabelPosition::isInConflictMultiPart( const LabelPosition *lp ) const
305 {
306  // check all parts against all parts of other one
307  const LabelPosition *tmp1 = this;
308  while ( tmp1 )
309  {
310  // check tmp1 against parts of other label
311  const LabelPosition *tmp2 = lp;
312  while ( tmp2 )
313  {
314  if ( tmp1->isInConflictSinglePart( tmp2 ) )
315  return true;
316  tmp2 = tmp2->nextPart();
317  }
318 
319  tmp1 = tmp1->nextPart();
320  }
321  return false; // no conflict found
322 }
323 
324 int LabelPosition::partCount() const
325 {
326  if ( mNextPart )
327  return mNextPart->partCount() + 1;
328  else
329  return 1;
330 }
331 
332 void LabelPosition::offsetPosition( double xOffset, double yOffset )
333 {
334  for ( int i = 0; i < 4; i++ )
335  {
336  x[i] += xOffset;
337  y[i] += yOffset;
338  }
339 
340  if ( mNextPart )
341  mNextPart->offsetPosition( xOffset, yOffset );
342 
343  invalidateGeos();
344 }
345 
347 {
348  return id;
349 }
350 
351 double LabelPosition::getX( int i ) const
352 {
353  return ( i >= 0 && i < 4 ? x[i] : -1 );
354 }
355 
356 double LabelPosition::getY( int i ) const
357 {
358  return ( i >= 0 && i < 4 ? y[i] : -1 );
359 }
360 
362 {
363  return alpha;
364 }
365 
367 {
368  if ( mCost >= 1 )
369  {
370  mCost -= int ( mCost ); // label cost up to 1
371  }
372 }
373 
375 {
376  return feature;
377 }
378 
379 void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
380 {
381  if ( mNextPart )
382  {
383  mNextPart->getBoundingBox( amin, amax );
384  }
385  else
386  {
387  amin[0] = std::numeric_limits<double>::max();
388  amax[0] = std::numeric_limits<double>::lowest();
389  amin[1] = std::numeric_limits<double>::max();
390  amax[1] = std::numeric_limits<double>::lowest();
391  }
392  for ( int c = 0; c < 4; c++ )
393  {
394  if ( x[c] < amin[0] )
395  amin[0] = x[c];
396  if ( x[c] > amax[0] )
397  amax[0] = x[c];
398  if ( y[c] < amin[1] )
399  amin[1] = y[c];
400  if ( y[c] > amax[1] )
401  amax[1] = y[c];
402  }
403 }
404 
406 {
407  mHasObstacleConflict = conflicts;
408  if ( mNextPart )
409  mNextPart->setConflictsWithObstacle( conflicts );
410 }
411 
413 {
414  mHasHardConflict = conflicts;
415  if ( mNextPart )
416  mNextPart->setHasHardObstacleConflict( conflicts );
417 }
418 
420 {
421  double amin[2];
422  double amax[2];
423  getBoundingBox( amin, amax );
424  index.remove( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
425 }
426 
428 {
429  double amin[2];
430  double amax[2];
431  getBoundingBox( amin, amax );
432  index.insert( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
433 }
434 
435 double LabelPosition::getDistanceToPoint( double xp, double yp ) const
436 {
437  //first check if inside, if so then distance is -1
438  bool contains = false;
439  if ( alpha == 0 )
440  {
441  // easy case -- horizontal label
442  contains = x[0] <= xp && x[1] >= xp && y[0] <= yp && y[2] >= yp;
443  }
444  else
445  {
446  contains = containsPoint( xp, yp );
447  }
448 
449  double distance = -1;
450  if ( !contains )
451  {
452  if ( alpha == 0 )
453  {
454  const double dx = std::max( std::max( x[0] - xp, 0.0 ), xp - x[1] );
455  const double dy = std::max( std::max( y[0] - yp, 0.0 ), yp - y[2] );
456  distance = std::sqrt( dx * dx + dy * dy );
457  }
458  else
459  {
460  distance = std::sqrt( minDistanceToPoint( xp, yp ) );
461  }
462  }
463 
464  if ( mNextPart && distance > 0 )
465  return std::min( distance, mNextPart->getDistanceToPoint( xp, yp ) );
466 
467  return distance;
468 }
469 
471 {
472  if ( !mGeos )
473  createGeosGeom();
474 
475  if ( !line->mGeos )
476  line->createGeosGeom();
477 
478  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
479  try
480  {
481  if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mGeos ) == 1 )
482  {
483  return true;
484  }
485  else if ( mNextPart )
486  {
487  return mNextPart->crossesLine( line );
488  }
489  }
490  catch ( GEOSException &e )
491  {
492  qWarning( "GEOS exception: %s", e.what() );
493  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
494  return false;
495  }
496 
497  return false;
498 }
499 
501 {
502  if ( !mGeos )
503  createGeosGeom();
504 
505  if ( !polygon->mGeos )
506  polygon->createGeosGeom();
507 
508  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
509  try
510  {
511  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1
512  && GEOSPreparedContains_r( geosctxt, polygon->preparedGeom(), mGeos ) != 1 )
513  {
514  return true;
515  }
516  else if ( mNextPart )
517  {
518  return mNextPart->crossesBoundary( polygon );
519  }
520  }
521  catch ( GEOSException &e )
522  {
523  qWarning( "GEOS exception: %s", e.what() );
524  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
525  return false;
526  }
527 
528  return false;
529 }
530 
532 {
533  //effectively take the average polygon intersection cost for all label parts
534  double totalCost = polygonIntersectionCostForParts( polygon );
535  int n = partCount();
536  return std::ceil( totalCost / n );
537 }
538 
540 {
541  if ( !mGeos )
542  createGeosGeom();
543 
544  if ( !polygon->mGeos )
545  polygon->createGeosGeom();
546 
547  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
548  try
549  {
550  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
551  {
552  return true;
553  }
554  }
555  catch ( GEOSException &e )
556  {
557  qWarning( "GEOS exception: %s", e.what() );
558  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
559  }
560 
561  if ( mNextPart )
562  {
563  return mNextPart->intersectsWithPolygon( polygon );
564  }
565  else
566  {
567  return false;
568  }
569 }
570 
571 double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
572 {
573  if ( !mGeos )
574  createGeosGeom();
575 
576  if ( !polygon->mGeos )
577  polygon->createGeosGeom();
578 
579  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
580  double cost = 0;
581  try
582  {
583  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
584  {
585  //at least a partial intersection
586  cost += 1;
587 
588  double px, py;
589 
590  // check each corner
591  for ( int i = 0; i < 4; ++i )
592  {
593  px = x[i];
594  py = y[i];
595 
596  for ( int a = 0; a < 2; ++a ) // and each middle of segment
597  {
598  if ( polygon->containsPoint( px, py ) )
599  cost++;
600  px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
601  py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
602  }
603  }
604 
605  px = ( x[0] + x[2] ) / 2.0;
606  py = ( y[0] + y[2] ) / 2.0;
607 
608  //check the label center. if covered by polygon, cost of 4
609  if ( polygon->containsPoint( px, py ) )
610  cost += 4;
611  }
612  }
613  catch ( GEOSException &e )
614  {
615  qWarning( "GEOS exception: %s", e.what() );
616  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
617  }
618 
619  //maintain scaling from 0 -> 12
620  cost = 12.0 * cost / 13.0;
621 
622  if ( mNextPart )
623  {
624  cost += mNextPart->polygonIntersectionCostForParts( polygon );
625  }
626 
627  return cost;
628 }
A rtree spatial index for use in the pal labeling engine.
Definition: palrtree.h:36
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
Definition: palrtree.h:59
void remove(T *data, const QgsRectangle &bounds)
Removes existing data from the spatial index, with the specified bounds.
Definition: palrtree.h:78
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2924
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).
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Main class to handle feature.
Definition: feature.h:97
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:152
bool showUprightLabels() const
Returns true if feature's label must be displayed upright.
Definition: feature.cpp:2283
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
bool intersectsWithPolygon(PointSet *polygon) const
Returns true if any intersection between polygon and position exists.
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
bool isIntersect(double *bbox)
Is the labelposition intersect the bounding-box ?
double getAlpha() const
Returns the angle to rotate text (in rad).
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:66
LabelPosition::Quadrant quadrant
void removeFromIndex(PalRtree< LabelPosition > &index)
Removes the label position from the specified index.
bool isInside(double *bbox)
Is the labelposition inside the bounding-box ?
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
int getId() const
Returns the id.
FeaturePart * feature
void validateCost()
Make sure the cost is less than 1.
bool isIn(double *bbox)
Is the labelposition in the bounding-box ? (intersect or inside????)
double cost() const
Returns the candidate label position's geographical cost.
void setConflictsWithObstacle(bool conflicts)
Sets whether the position is marked as conflicting with an obstacle feature.
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
void setHasHardObstacleConflict(bool conflicts)
Sets whether the position is marked as having a hard conflict with an obstacle feature.
bool crossesBoundary(PointSet *polygon) const
Returns true if this label crosses the boundary of the specified polygon.
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
double getDistanceToPoint(double xp, double yp) const
Gets distance from this label to a point. If point lies inside, returns negative number.
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double getX(int i=0) const
Returns the down-left x coordinate.
void getBoundingBox(double amin[2], double amax[2]) const
Returns bounding box - amin: xmin,ymin - amax: xmax,ymax.
double getY(int i=0) const
Returns the down-left y coordinate.
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
void insertIntoIndex(PalRtree< LabelPosition > &index)
Inserts the label position into the specified index.
int polygonIntersectionCost(PointSet *polygon) const
Returns cost of position intersection with polygon (testing area of intersection and center).
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:182
The underlying raw pal geometry class.
Definition: pointset.h:76
friend class LabelPosition
Definition: pointset.h:78
double ymax
Definition: pointset.h:235
double ymin
Definition: pointset.h:234
void createGeosGeom() const
Definition: pointset.cpp:117
std::vector< double > y
Definition: pointset.h:205
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:929
std::vector< double > x
Definition: pointset.h:204
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:167
GEOSGeometry * mGeos
Definition: pointset.h:208
double xmin
Definition: pointset.h:232
void invalidateGeos()
Definition: pointset.cpp:179
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:793
double xmax
Definition: pointset.h:233
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:257
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316