QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
217  return false;
218  }
219 
220  return false;
221 }
222 
223 bool LabelPosition::within( const GEOSPreparedGeometry *geometry )
224 {
225  if ( !mGeos )
226  createGeosGeom();
227 
228  try
229  {
230  if ( GEOSPreparedContains_r( QgsGeos::getGEOSHandler(), geometry, mGeos ) != 1 )
231  {
232  return false;
233  }
234  else if ( mNextPart )
235  {
236  return mNextPart->within( geometry );
237  }
238  }
239  catch ( GEOSException &e )
240  {
241  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
242  return false;
243  }
244 
245  return true;
246 }
247 
248 bool LabelPosition::isInside( double *bbox )
249 {
250  for ( int i = 0; i < 4; i++ )
251  {
252  if ( !( x[i] >= bbox[0] && x[i] <= bbox[2] &&
253  y[i] >= bbox[1] && y[i] <= bbox[3] ) )
254  return false;
255  }
256 
257  if ( mNextPart )
258  return mNextPart->isInside( bbox );
259  else
260  return true;
261 }
262 
264 {
265  if ( this->probFeat == lp->probFeat ) // bugfix #1
266  return false; // always overlaping itself !
267 
268  if ( !nextPart() && !lp->nextPart() )
269  return isInConflictSinglePart( lp );
270  else
271  return isInConflictMultiPart( lp );
272 }
273 
274 bool LabelPosition::isInConflictSinglePart( const LabelPosition *lp ) const
275 {
276  if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
277  {
278  // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
279  return boundingBoxIntersects( lp );
280  }
281 
282  if ( !mGeos )
283  createGeosGeom();
284 
285  if ( !lp->mGeos )
286  lp->createGeosGeom();
287 
288  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
289  try
290  {
291  bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedGeom(), lp->mGeos ) == 1 );
292  return result;
293  }
294  catch ( GEOSException &e )
295  {
296  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
297  return false;
298  }
299 }
300 
301 bool LabelPosition::isInConflictMultiPart( const LabelPosition *lp ) const
302 {
303  // check all parts against all parts of other one
304  const LabelPosition *tmp1 = this;
305  while ( tmp1 )
306  {
307  // check tmp1 against parts of other label
308  const LabelPosition *tmp2 = lp;
309  while ( tmp2 )
310  {
311  if ( tmp1->isInConflictSinglePart( tmp2 ) )
312  return true;
313  tmp2 = tmp2->nextPart();
314  }
315 
316  tmp1 = tmp1->nextPart();
317  }
318  return false; // no conflict found
319 }
320 
321 int LabelPosition::partCount() const
322 {
323  if ( mNextPart )
324  return mNextPart->partCount() + 1;
325  else
326  return 1;
327 }
328 
329 void LabelPosition::offsetPosition( double xOffset, double yOffset )
330 {
331  for ( int i = 0; i < 4; i++ )
332  {
333  x[i] += xOffset;
334  y[i] += yOffset;
335  }
336 
337  if ( mNextPart )
338  mNextPart->offsetPosition( xOffset, yOffset );
339 
340  invalidateGeos();
341 }
342 
344 {
345  return id;
346 }
347 
348 double LabelPosition::getX( int i ) const
349 {
350  return ( i >= 0 && i < 4 ? x[i] : -1 );
351 }
352 
353 double LabelPosition::getY( int i ) const
354 {
355  return ( i >= 0 && i < 4 ? y[i] : -1 );
356 }
357 
359 {
360  return alpha;
361 }
362 
364 {
365  if ( mCost >= 1 )
366  {
367  mCost -= int ( mCost ); // label cost up to 1
368  }
369 }
370 
372 {
373  return feature;
374 }
375 
376 void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
377 {
378  if ( mNextPart )
379  {
380  mNextPart->getBoundingBox( amin, amax );
381  }
382  else
383  {
384  amin[0] = std::numeric_limits<double>::max();
385  amax[0] = std::numeric_limits<double>::lowest();
386  amin[1] = std::numeric_limits<double>::max();
387  amax[1] = std::numeric_limits<double>::lowest();
388  }
389  for ( int c = 0; c < 4; c++ )
390  {
391  if ( x[c] < amin[0] )
392  amin[0] = x[c];
393  if ( x[c] > amax[0] )
394  amax[0] = x[c];
395  if ( y[c] < amin[1] )
396  amin[1] = y[c];
397  if ( y[c] > amax[1] )
398  amax[1] = y[c];
399  }
400 }
401 
403 {
404  mHasObstacleConflict = conflicts;
405  if ( mNextPart )
406  mNextPart->setConflictsWithObstacle( conflicts );
407 }
408 
410 {
411  mHasHardConflict = conflicts;
412  if ( mNextPart )
413  mNextPart->setHasHardObstacleConflict( conflicts );
414 }
415 
417 {
418  double amin[2];
419  double amax[2];
420  getBoundingBox( amin, amax );
421  index.remove( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
422 }
423 
425 {
426  double amin[2];
427  double amax[2];
428  getBoundingBox( amin, amax );
429  index.insert( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
430 }
431 
432 double LabelPosition::getDistanceToPoint( double xp, double yp ) const
433 {
434  //first check if inside, if so then distance is -1
435  double distance = ( containsPoint( xp, yp ) ? -1
436  : std::sqrt( minDistanceToPoint( xp, yp ) ) );
437 
438  if ( mNextPart && distance > 0 )
439  return std::min( distance, mNextPart->getDistanceToPoint( xp, yp ) );
440 
441  return distance;
442 }
443 
445 {
446  if ( !mGeos )
447  createGeosGeom();
448 
449  if ( !line->mGeos )
450  line->createGeosGeom();
451 
452  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
453  try
454  {
455  if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mGeos ) == 1 )
456  {
457  return true;
458  }
459  else if ( mNextPart )
460  {
461  return mNextPart->crossesLine( line );
462  }
463  }
464  catch ( GEOSException &e )
465  {
466  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
467  return false;
468  }
469 
470  return false;
471 }
472 
474 {
475  if ( !mGeos )
476  createGeosGeom();
477 
478  if ( !polygon->mGeos )
479  polygon->createGeosGeom();
480 
481  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
482  try
483  {
484  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1
485  && GEOSPreparedContains_r( geosctxt, polygon->preparedGeom(), mGeos ) != 1 )
486  {
487  return true;
488  }
489  else if ( mNextPart )
490  {
491  return mNextPart->crossesBoundary( polygon );
492  }
493  }
494  catch ( GEOSException &e )
495  {
496  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
497  return false;
498  }
499 
500  return false;
501 }
502 
504 {
505  //effectively take the average polygon intersection cost for all label parts
506  double totalCost = polygonIntersectionCostForParts( polygon );
507  int n = partCount();
508  return std::ceil( totalCost / n );
509 }
510 
512 {
513  if ( !mGeos )
514  createGeosGeom();
515 
516  if ( !polygon->mGeos )
517  polygon->createGeosGeom();
518 
519  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
520  try
521  {
522  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
523  {
524  return true;
525  }
526  }
527  catch ( GEOSException &e )
528  {
529  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
530  }
531 
532  if ( mNextPart )
533  {
534  return mNextPart->intersectsWithPolygon( polygon );
535  }
536  else
537  {
538  return false;
539  }
540 }
541 
542 double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
543 {
544  if ( !mGeos )
545  createGeosGeom();
546 
547  if ( !polygon->mGeos )
548  polygon->createGeosGeom();
549 
550  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
551  double cost = 0;
552  try
553  {
554  if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mGeos ) == 1 )
555  {
556  //at least a partial intersection
557  cost += 1;
558 
559  double px, py;
560 
561  // check each corner
562  for ( int i = 0; i < 4; ++i )
563  {
564  px = x[i];
565  py = y[i];
566 
567  for ( int a = 0; a < 2; ++a ) // and each middle of segment
568  {
569  if ( polygon->containsPoint( px, py ) )
570  cost++;
571  px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
572  py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
573  }
574  }
575 
576  px = ( x[0] + x[2] ) / 2.0;
577  py = ( y[0] + y[2] ) / 2.0;
578 
579  //check the label center. if covered by polygon, cost of 4
580  if ( polygon->containsPoint( px, py ) )
581  cost += 4;
582  }
583  }
584  catch ( GEOSException &e )
585  {
586  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
587  }
588 
589  //maintain scaling from 0 -> 12
590  cost = 12.0 * cost / 13.0;
591 
592  if ( mNextPart )
593  {
594  cost += mNextPart->polygonIntersectionCostForParts( polygon );
595  }
596 
597  return cost;
598 }
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
FeaturePart * feature
A rectangle specified with double values.
Definition: qgsrectangle.h:41
void invalidateGeos()
Definition: pointset.cpp:179
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:246
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:315
void offsetPosition(double xOffset, double yOffset)
Shift the label by specified offset.
std::vector< double > x
Definition: pointset.h:201
void createGeosGeom() const
Definition: pointset.cpp:117
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
void setHasHardObstacleConflict(bool conflicts)
Sets whether the position is marked as having a hard conflict with an obstacle feature.
A rtree spatial index for use in the pal labeling engine.
Definition: palrtree.h:35
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2872
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:889
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
double cost() const
Returns the candidate label position&#39;s geographical cost.
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double ymax
Definition: pointset.h:232
double xmin
Definition: pointset.h:229
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:231
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:151
void validateCost()
Make sure the cost is less than 1.
Main class to handle feature.
Definition: feature.h:95
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.
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:205
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:167
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
Definition: palrtree.h:59
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
void remove(T *data, const QgsRectangle &bounds)
Removes existing data from the spatial index, with the specified bounds.
Definition: palrtree.h:78
bool crossesBoundary(PointSet *polygon) const
Returns true if this label crosses the boundary of the specified polygon.
void removeFromIndex(PalRtree< LabelPosition > &index)
Removes the label position from the specified index.
double xmax
Definition: pointset.h:230
std::vector< double > y
Definition: pointset.h:202
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:788
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:182
bool showUprightLabels() const
Returns true if feature&#39;s label must be displayed upright.
Definition: feature.cpp:1962
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
LabelPosition::Quadrant quadrant
void insertIntoIndex(PalRtree< LabelPosition > &index)
Inserts the label position into the specified index.
double getDistanceToPoint(double xp, double yp) const
Gets distance from this label to a point. If point lies inside, returns negative number.