QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
problem.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 "pal.h"
31 #include "palstat.h"
32 #include "layer.h"
33 #include "feature.h"
34 #include "geomfunction.h"
35 #include "labelposition.h"
36 #include "problem.h"
37 #include "util.h"
38 #include "priorityqueue.h"
39 #include "internalexception.h"
40 #include <cfloat>
41 #include <limits> //for std::numeric_limits<int>::max()
42 
43 #include "qgslabelingengine.h"
44 
45 using namespace pal;
46 
47 inline void delete_chain( Chain *chain )
48 {
49  if ( chain )
50  {
51  delete[] chain->feat;
52  delete[] chain->label;
53  delete chain;
54  }
55 }
56 
58  : mAllCandidatesIndex( extent )
59  , mActiveCandidatesIndex( extent )
60 {
61 
62 }
63 
64 Problem::~Problem() = default;
65 
67 {
68  int i;
69  int j;
70  int k;
71 
72  int counter = 0;
73 
74  int lpid;
75 
76  bool *ok = new bool[mTotalCandidates];
77  bool run = true;
78 
79  for ( i = 0; i < mTotalCandidates; i++ )
80  ok[i] = false;
81 
82 
83  double amin[2];
84  double amax[2];
85  LabelPosition *lp2 = nullptr;
86 
87  while ( run )
88  {
89  run = false;
90  for ( i = 0; i < static_cast< int >( mFeatureCount ); i++ )
91  {
92  // ok[i] = true;
93  for ( j = 0; j < mFeatNbLp[i]; j++ ) // foreach candidate
94  {
95  if ( !ok[mFeatStartId[i] + j] )
96  {
97  if ( mLabelPositions.at( mFeatStartId[i] + j )->getNumOverlaps() == 0 ) // if candidate has no overlap
98  {
99  run = true;
100  ok[mFeatStartId[i] + j] = true;
101  // 1) remove worse candidates from candidates
102  // 2) update nb_overlaps
103  counter += mFeatNbLp[i] - j - 1;
104 
105  for ( k = j + 1; k < mFeatNbLp[i]; k++ )
106  {
107 
108  lpid = mFeatStartId[i] + k;
109  ok[lpid] = true;
110  lp2 = mLabelPositions[lpid ].get();
111 
112  lp2->getBoundingBox( amin, amax );
113 
114  mNbOverlap -= lp2->getNumOverlaps();
115  mAllCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&lp2]( const LabelPosition * lp ) -> bool
116  {
117  if ( lp2->isInConflict( lp ) )
118  {
119  const_cast< LabelPosition * >( lp )->decrementNumOverlaps();
120  lp2->decrementNumOverlaps();
121  }
122 
123  return true;
124  } );
125  lp2->removeFromIndex( mAllCandidatesIndex );
126  }
127 
128  mFeatNbLp[i] = j + 1;
129  break;
130  }
131  }
132  }
133  }
134  }
135 
136  this->mTotalCandidates -= counter;
137  delete[] ok;
138 }
139 
140 void ignoreLabel( const LabelPosition *lp, PriorityQueue &list, PalRtree< LabelPosition > &candidatesIndex )
141 {
142  if ( list.isIn( lp->getId() ) )
143  {
144  list.remove( lp->getId() );
145 
146  double amin[2];
147  double amax[2];
148  lp->getBoundingBox( amin, amax );
149  candidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [lp, &list]( const LabelPosition * lp2 )->bool
150  {
151  if ( lp2->getId() != lp->getId() && list.isIn( lp2->getId() ) && lp2->isInConflict( lp ) )
152  {
153  list.decreaseKey( lp2->getId() );
154  }
155  return true;
156  } );
157  }
158 }
159 
160 /* Better initial solution
161  * Step one FALP (Yamamoto, Camara, Lorena 2005)
162  */
164 {
165  int label;
166 
167  mSol.init( mFeatureCount );
168 
169  PriorityQueue list( mTotalCandidates, mAllNblp, true );
170 
171  double amin[2];
172  double amax[2];
173 
174  LabelPosition *lp = nullptr;
175 
176  for ( int i = 0; i < static_cast< int >( mFeatureCount ); i++ )
177  for ( int j = 0; j < mFeatNbLp[i]; j++ )
178  {
179  label = mFeatStartId[i] + j;
180  try
181  {
182  list.insert( label, mLabelPositions.at( label )->getNumOverlaps() );
183  }
184  catch ( pal::InternalException::Full & )
185  {
186  continue;
187  }
188  }
189 
190  while ( list.getSize() > 0 ) // O (log size)
191  {
192  if ( pal->isCanceled() )
193  {
194  return;
195  }
196 
197  label = list.getBest(); // O (log size)
198 
199  lp = mLabelPositions[ label ].get();
200 
201  if ( lp->getId() != label )
202  {
203  //error
204  }
205 
206  int probFeatId = lp->getProblemFeatureId();
207  mSol.activeLabelIds[probFeatId] = label;
208 
209  for ( int i = mFeatStartId[probFeatId]; i < mFeatStartId[probFeatId] + mFeatNbLp[probFeatId]; i++ )
210  {
211  ignoreLabel( mLabelPositions[ i ].get(), list, mAllCandidatesIndex );
212  }
213 
214 
215  lp->getBoundingBox( amin, amax );
216 
217  std::vector< const LabelPosition * > conflictingPositions;
218  mAllCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [lp, &conflictingPositions]( const LabelPosition * lp2 ) ->bool
219  {
220  if ( lp->isInConflict( lp2 ) )
221  {
222  conflictingPositions.emplace_back( lp2 );
223  }
224  return true;
225  } );
226 
227  for ( const LabelPosition *conflict : conflictingPositions )
228  {
229  ignoreLabel( conflict, list, mAllCandidatesIndex );
230  }
231 
232  mActiveCandidatesIndex.insert( lp, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
233  }
234 
235  if ( mDisplayAll )
236  {
237  int nbOverlap;
238  int start_p;
239  LabelPosition *retainedLabel = nullptr;
240  int p;
241 
242  for ( std::size_t i = 0; i < mFeatureCount; i++ ) // forearch hidden feature
243  {
244  if ( mSol.activeLabelIds[i] == -1 )
245  {
246  nbOverlap = std::numeric_limits<int>::max();
247  start_p = mFeatStartId[i];
248  for ( p = 0; p < mFeatNbLp[i]; p++ )
249  {
250  lp = mLabelPositions[ start_p + p ].get();
251  lp->resetNumOverlaps();
252 
253  lp->getBoundingBox( amin, amax );
254 
255 
256  mActiveCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&lp]( const LabelPosition * lp2 )->bool
257  {
258  if ( lp->isInConflict( lp2 ) )
259  {
260  lp->incrementNumOverlaps();
261  }
262  return true;
263  } );
264 
265  if ( lp->getNumOverlaps() < nbOverlap )
266  {
267  retainedLabel = lp;
268  nbOverlap = lp->getNumOverlaps();
269  }
270  }
271  mSol.activeLabelIds[i] = retainedLabel->getId();
272 
273  retainedLabel->insertIntoIndex( mActiveCandidatesIndex );
274 
275  }
276  }
277  }
278 }
279 
280 inline Chain *Problem::chain( int seed )
281 {
282  int lid;
283 
284  double delta;
285  double delta_min;
286  double delta_best = std::numeric_limits<double>::max();
287  double delta_tmp;
288 
289  int next_seed;
290  int retainedLabel;
291  Chain *retainedChain = nullptr;
292 
293  int max_degree = pal->mEjChainDeg;
294 
295  int seedNbLp;
296 
297  QLinkedList<ElemTrans *> currentChain;
298  QLinkedList<int> conflicts;
299 
300  std::vector< int > tmpsol( mSol.activeLabelIds );
301 
302  LabelPosition *lp = nullptr;
303 
304  double amin[2];
305  double amax[2];
306 
307  delta = 0;
308  while ( seed != -1 )
309  {
310  seedNbLp = mFeatNbLp[seed];
311  delta_min = std::numeric_limits<double>::max();
312 
313  next_seed = -1;
314  retainedLabel = -2;
315 
316  // sol[seed] is ejected
317  if ( tmpsol[seed] == -1 )
318  delta -= mInactiveCost[seed];
319  else
320  delta -= mLabelPositions.at( tmpsol[seed] )->cost();
321 
322  for ( int i = -1; i < seedNbLp; i++ )
323  {
324  try
325  {
326  // Skip active label !
327  if ( !( tmpsol[seed] == -1 && i == -1 ) && i + mFeatStartId[seed] != tmpsol[seed] )
328  {
329  if ( i != -1 ) // new_label
330  {
331  lid = mFeatStartId[seed] + i;
332  delta_tmp = delta;
333 
334  lp = mLabelPositions[ lid ].get();
335 
336  // evaluate conflicts graph in solution after moving seed's label
337 
338  lp->getBoundingBox( amin, amax );
339  mActiveCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [lp, &delta_tmp, &conflicts, &currentChain, this]( const LabelPosition * lp2 ) -> bool
340  {
341  if ( lp2->isInConflict( lp ) )
342  {
343  const int feat = lp2->getProblemFeatureId();
344 
345  // is there any cycles ?
346  QLinkedList< ElemTrans * >::iterator cur;
347  for ( cur = currentChain.begin(); cur != currentChain.end(); ++cur )
348  {
349  if ( ( *cur )->feat == feat )
350  {
351  throw - 1;
352  }
353  }
354 
355  if ( !conflicts.contains( feat ) )
356  {
357  conflicts.append( feat );
358  delta_tmp += lp2->cost() + mInactiveCost[feat];
359  }
360  }
361  return true;
362  } );
363 
364  // no conflict -> end of chain
365  if ( conflicts.isEmpty() )
366  {
367  if ( !retainedChain || delta + lp->cost() < delta_best )
368  {
369  if ( retainedChain )
370  {
371  delete[] retainedChain->label;
372  delete[] retainedChain->feat;
373  }
374  else
375  {
376  retainedChain = new Chain();
377  }
378 
379  delta_best = delta + lp->cost();
380 
381  retainedChain->degree = currentChain.size() + 1;
382  retainedChain->feat = new int[retainedChain->degree];
383  retainedChain->label = new int[retainedChain->degree];
384  QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
385  ElemTrans *move = nullptr;
386  int j = 0;
387  while ( current != currentChain.end() )
388  {
389  move = *current;
390  retainedChain->feat[j] = move->feat;
391  retainedChain->label[j] = move->new_label;
392  ++current;
393  ++j;
394  }
395  retainedChain->feat[j] = seed;
396  retainedChain->label[j] = lid;
397  retainedChain->delta = delta + lp->cost();
398  }
399  }
400 
401  // another feature can be ejected
402  else if ( conflicts.size() == 1 )
403  {
404  if ( delta_tmp < delta_min )
405  {
406  delta_min = delta_tmp;
407  retainedLabel = lid;
408  next_seed = conflicts.takeFirst();
409  }
410  else
411  {
412  conflicts.takeFirst();
413  }
414  }
415  else
416  {
417 
418  // A lot of conflict : make them inactive and store chain
419  Chain *newChain = new Chain();
420  newChain->degree = currentChain.size() + 1 + conflicts.size();
421  newChain->feat = new int[newChain->degree];
422  newChain->label = new int[newChain->degree];
423  QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
424  ElemTrans *move = nullptr;
425  int j = 0;
426 
427  while ( current != currentChain.end() )
428  {
429  move = *current;
430  newChain->feat[j] = move->feat;
431  newChain->label[j] = move->new_label;
432  ++current;
433  ++j;
434  }
435 
436  // add the current candidates into the chain
437  newChain->feat[j] = seed;
438  newChain->label[j] = lid;
439  newChain->delta = delta + mLabelPositions.at( newChain->label[j] )->cost();
440  j++;
441 
442  // hide all conflictual candidates
443  while ( !conflicts.isEmpty() )
444  {
445  int ftid = conflicts.takeFirst();
446  newChain->feat[j] = ftid;
447  newChain->label[j] = -1;
448  newChain->delta += mInactiveCost[ftid];
449  j++;
450  }
451 
452  if ( newChain->delta < delta_best )
453  {
454  if ( retainedChain )
455  delete_chain( retainedChain );
456 
457  delta_best = newChain->delta;
458  retainedChain = newChain;
459  }
460  else
461  {
462  delete_chain( newChain );
463  }
464  }
465 
466  }
467  else // Current label == -1 end of chain ...
468  {
469  if ( !retainedChain || delta + mInactiveCost[seed] < delta_best )
470  {
471  if ( retainedChain )
472  {
473  delete[] retainedChain->label;
474  delete[] retainedChain->feat;
475  }
476  else
477  retainedChain = new Chain();
478 
479  delta_best = delta + mInactiveCost[seed];
480 
481  retainedChain->degree = currentChain.size() + 1;
482  retainedChain->feat = new int[retainedChain->degree];
483  retainedChain->label = new int[retainedChain->degree];
484  QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
485  ElemTrans *move = nullptr;
486  int j = 0;
487  while ( current != currentChain.end() )
488  {
489  move = *current;
490  retainedChain->feat[j] = move->feat;
491  retainedChain->label[j] = move->new_label;
492  ++current;
493  ++j;
494  }
495  retainedChain->feat[j] = seed;
496  retainedChain->label[j] = -1;
497  retainedChain->delta = delta + mInactiveCost[seed];
498  }
499  }
500  }
501  }
502  catch ( int )
503  {
504  conflicts.clear();
505  }
506  } // end foreach labelposition
507 
508  if ( next_seed == -1 )
509  {
510  seed = -1;
511  }
512  else if ( currentChain.size() > max_degree )
513  {
514  // Max degree reached
515  seed = -1;
516  }
517  else
518  {
519  ElemTrans *et = new ElemTrans();
520  et->feat = seed;
521  et->old_label = tmpsol[seed];
522  et->new_label = retainedLabel;
523  currentChain.append( et );
524 
525  if ( et->old_label != -1 )
526  {
527  mLabelPositions.at( et->old_label )->removeFromIndex( mActiveCandidatesIndex );
528  }
529 
530  if ( et->new_label != -1 )
531  {
532  mLabelPositions.at( et->new_label )->insertIntoIndex( mActiveCandidatesIndex );
533  }
534 
535 
536  tmpsol[seed] = retainedLabel;
537  // cppcheck-suppress invalidFunctionArg
538  delta += mLabelPositions.at( retainedLabel )->cost();
539  seed = next_seed;
540  }
541  }
542 
543  while ( !currentChain.isEmpty() )
544  {
545  std::unique_ptr< ElemTrans > et( currentChain.takeFirst() );
546 
547  if ( et->new_label != -1 )
548  {
549  mLabelPositions.at( et->new_label )->removeFromIndex( mActiveCandidatesIndex );
550  }
551 
552  if ( et->old_label != -1 )
553  {
554  mLabelPositions.at( et->old_label )->insertIntoIndex( mActiveCandidatesIndex );
555  }
556  }
557 
558  return retainedChain;
559 }
560 
561 
562 void Problem::chain_search()
563 {
564  if ( mFeatureCount == 0 )
565  return;
566 
567  int i;
568  int seed;
569  bool *ok = new bool[mFeatureCount];
570  int fid;
571  int lid;
572  int popit = 0;
573 
574  Chain *retainedChain = nullptr;
575 
576  std::fill( ok, ok + mFeatureCount, false );
577 
578  //initialization();
579  init_sol_falp();
580 
581  //check_solution();
582  solution_cost();
583 
584  int iter = 0;
585 
586  double amin[2];
587  double amax[2];
588 
589  while ( true )
590  {
591 
592  //check_solution();
593 
594  for ( seed = ( iter + 1 ) % mFeatureCount;
595  ok[seed] && seed != iter;
596  seed = ( seed + 1 ) % mFeatureCount )
597  ;
598 
599  // All seeds are OK
600  if ( seed == iter )
601  {
602  break;
603  }
604 
605  iter = ( iter + 1 ) % mFeatureCount;
606  retainedChain = chain( seed );
607 
608  if ( retainedChain && retainedChain->delta < - EPSILON )
609  {
610  // apply modification
611  for ( i = 0; i < retainedChain->degree; i++ )
612  {
613  fid = retainedChain->feat[i];
614  lid = retainedChain->label[i];
615 
616  if ( mSol.activeLabelIds[fid] >= 0 )
617  {
618  LabelPosition *old = mLabelPositions[ mSol.activeLabelIds[fid] ].get();
619  old->removeFromIndex( mActiveCandidatesIndex );
620  old->getBoundingBox( amin, amax );
621  mAllCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&ok, old]( const LabelPosition * lp ) ->bool
622  {
623  if ( old->isInConflict( lp ) )
624  {
625  ok[lp->getProblemFeatureId()] = false;
626  }
627 
628  return true;
629  } );
630  }
631 
632  mSol.activeLabelIds[fid] = lid;
633 
634  if ( mSol.activeLabelIds[fid] >= 0 )
635  {
636  mLabelPositions.at( lid )->insertIntoIndex( mActiveCandidatesIndex );
637  }
638 
639  ok[fid] = false;
640  }
641  mSol.totalCost += retainedChain->delta;
642  }
643  else
644  {
645  // no chain or the one is not god enough
646  ok[seed] = true;
647  }
648 
649  delete_chain( retainedChain );
650  popit++;
651  }
652 
653  solution_cost();
654  delete[] ok;
655 }
656 
657 QList<LabelPosition *> Problem::getSolution( bool returnInactive, QList<LabelPosition *> *unlabeled )
658 {
659  QList<LabelPosition *> finalLabelPlacements;
660 
661  // loop through all features to be labeled
662  for ( std::size_t i = 0; i < mFeatureCount; i++ )
663  {
664  const int labelId = mSol.activeLabelIds[i];
665  const bool foundNonOverlappingPlacement = labelId != -1;
666  const int startIndexForLabelPlacements = mFeatStartId[i];
667  const bool foundCandidatesForFeature = startIndexForLabelPlacements < static_cast< int >( mLabelPositions.size() );
668 
669  if ( foundNonOverlappingPlacement )
670  {
671  finalLabelPlacements.push_back( mLabelPositions[ labelId ].get() ); // active labels
672  }
673  else if ( foundCandidatesForFeature &&
674  ( returnInactive // allowing any overlapping labels regardless of where they are from
675  || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->layer()->displayAll() // allowing overlapping labels for the layer
676  || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->alwaysShow() ) ) // allowing overlapping labels for the feature
677  {
678  finalLabelPlacements.push_back( mLabelPositions[ startIndexForLabelPlacements ].get() ); // unplaced label
679  }
680  else if ( unlabeled )
681  {
682  // need to be careful here -- if the next feature's start id is the same as this one, then this feature had no candidates!
683  if ( foundCandidatesForFeature && ( i == mFeatureCount - 1 || startIndexForLabelPlacements != mFeatStartId[i + 1] ) )
684  unlabeled->push_back( mLabelPositions[ startIndexForLabelPlacements ].get() );
685  }
686  }
687 
688  // unlabeled features also include those with no candidates
689  if ( unlabeled )
690  {
691  for ( const std::unique_ptr< LabelPosition > &position : mPositionsWithNoCandidates )
692  unlabeled->append( position.get() );
693  }
694 
695  return finalLabelPlacements;
696 }
697 
698 void Problem::solution_cost()
699 {
700  mSol.totalCost = 0.0;
701 
702  LabelPosition *lp = nullptr;
703 
704  double amin[2];
705  double amax[2];
706 
707  for ( std::size_t i = 0; i < mFeatureCount; i++ )
708  {
709  if ( mSol.activeLabelIds[i] == -1 )
710  {
711  mSol.totalCost += mInactiveCost[i];
712  }
713  else
714  {
715  lp = mLabelPositions[ mSol.activeLabelIds[i] ].get();
716 
717  lp->getBoundingBox( amin, amax );
718  mActiveCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&lp, this]( const LabelPosition * lp2 )->bool
719  {
720  if ( lp->isInConflict( lp2 ) )
721  {
722  mSol.totalCost += mInactiveCost[lp2->getProblemFeatureId()] + lp2->cost();
723  }
724 
725  return true;
726  } );
727 
728  mSol.totalCost += lp->cost();
729  }
730  }
731 }
A rtree spatial index for use in the pal labeling engine.
Definition: palrtree.h:36
bool intersects(const QgsRectangle &bounds, const std::function< bool(T *data)> &callback) const
Performs an intersection check against the index, for data intersecting the specified bounds.
Definition: palrtree.h:96
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Thrown when something is added in a Full set.
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
void removeFromIndex(PalRtree< LabelPosition > &index)
Removes the label position from the specified index.
void decrementNumOverlaps()
Decreases the number of overlaps recorded against this position by 1.
int getId() const
Returns the id.
double cost() const
Returns the candidate label position's geographical cost.
int getNumOverlaps() const
int getProblemFeatureId() const
void getBoundingBox(double amin[2], double amax[2]) const
Returns bounding box - amin: xmin,ymin - amax: xmax,ymax.
void insertIntoIndex(PalRtree< LabelPosition > &index)
Inserts the label position into the specified index.
Custom priority queue for use in pal labeling engine.
Definition: priorityqueue.h:53
void insert(int key, double p)
void remove(int key)
bool isIn(int key)
QList< LabelPosition * > getSolution(bool returnInactive, QList< LabelPosition * > *unlabeled=nullptr)
Solves the labeling problem, selecting the best candidate locations for all labels and returns a list...
Definition: problem.cpp:657
Problem(const QgsRectangle &extent)
Constructor for Problem.
Definition: problem.cpp:57
void init_sol_falp()
Definition: problem.cpp:163
void reduce()
Definition: problem.cpp:66
void delete_chain(Chain *chain)
Definition: problem.cpp:47
void ignoreLabel(const LabelPosition *lp, PriorityQueue &list, PalRtree< LabelPosition > &candidatesIndex)
Definition: problem.cpp:140
double delta
Definition: problem.h:59
int * label
Definition: problem.h:61
int * feat
Definition: problem.h:60
int degree
Definition: problem.h:58
int feat
Definition: util.h:68
int new_label
Definition: util.h:70
int old_label
Definition: util.h:69
#define EPSILON
Definition: util.h:78