QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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  delta += mLabelPositions.at( retainedLabel )->cost();
538  seed = next_seed;
539  }
540  }
541 
542  while ( !currentChain.isEmpty() )
543  {
544  std::unique_ptr< ElemTrans > et( currentChain.takeFirst() );
545 
546  if ( et->new_label != -1 )
547  {
548  mLabelPositions.at( et->new_label )->removeFromIndex( mActiveCandidatesIndex );
549  }
550 
551  if ( et->old_label != -1 )
552  {
553  mLabelPositions.at( et->old_label )->insertIntoIndex( mActiveCandidatesIndex );
554  }
555  }
556 
557  return retainedChain;
558 }
559 
560 
562 {
563  if ( mFeatureCount == 0 )
564  return;
565 
566  int i;
567  int seed;
568  bool *ok = new bool[mFeatureCount];
569  int fid;
570  int lid;
571  int popit = 0;
572 
573  Chain *retainedChain = nullptr;
574 
575  std::fill( ok, ok + mFeatureCount, false );
576 
577  //initialization();
578  init_sol_falp();
579 
580  //check_solution();
581  solution_cost();
582 
583  int iter = 0;
584 
585  double amin[2];
586  double amax[2];
587 
588  while ( true )
589  {
590 
591  //check_solution();
592 
593  for ( seed = ( iter + 1 ) % mFeatureCount;
594  ok[seed] && seed != iter;
595  seed = ( seed + 1 ) % mFeatureCount )
596  ;
597 
598  // All seeds are OK
599  if ( seed == iter )
600  {
601  break;
602  }
603 
604  iter = ( iter + 1 ) % mFeatureCount;
605  retainedChain = chain( seed );
606 
607  if ( retainedChain && retainedChain->delta < - EPSILON )
608  {
609  // apply modification
610  for ( i = 0; i < retainedChain->degree; i++ )
611  {
612  fid = retainedChain->feat[i];
613  lid = retainedChain->label[i];
614 
615  if ( mSol.activeLabelIds[fid] >= 0 )
616  {
617  LabelPosition *old = mLabelPositions[ mSol.activeLabelIds[fid] ].get();
618  old->removeFromIndex( mActiveCandidatesIndex );
619  old->getBoundingBox( amin, amax );
620  mAllCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&ok, old]( const LabelPosition * lp ) ->bool
621  {
622  if ( old->isInConflict( lp ) )
623  {
624  ok[lp->getProblemFeatureId()] = false;
625  }
626 
627  return true;
628  } );
629  }
630 
631  mSol.activeLabelIds[fid] = lid;
632 
633  if ( mSol.activeLabelIds[fid] >= 0 )
634  {
635  mLabelPositions.at( lid )->insertIntoIndex( mActiveCandidatesIndex );
636  }
637 
638  ok[fid] = false;
639  }
640  mSol.totalCost += retainedChain->delta;
641  }
642  else
643  {
644  // no chain or the one is not god enough
645  ok[seed] = true;
646  }
647 
648  delete_chain( retainedChain );
649  popit++;
650  }
651 
652  solution_cost();
653  delete[] ok;
654 }
655 
656 QList<LabelPosition *> Problem::getSolution( bool returnInactive, QList<LabelPosition *> *unlabeled )
657 {
658  QList<LabelPosition *> solList;
659 
660  for ( std::size_t i = 0; i < mFeatureCount; i++ )
661  {
662  if ( mSol.activeLabelIds[i] != -1 )
663  {
664  solList.push_back( mLabelPositions[ mSol.activeLabelIds[i] ].get() ); // active labels
665  }
666  else if ( returnInactive
667  || ( mFeatStartId[i] < static_cast< int >( mLabelPositions.size() ) &&
668  ( mLabelPositions.at( mFeatStartId[i] )->getFeaturePart()->layer()->displayAll()
669  || mLabelPositions.at( mFeatStartId[i] )->getFeaturePart()->alwaysShow() ) ) )
670  {
671  solList.push_back( mLabelPositions[ mFeatStartId[i] ].get() ); // unplaced label
672  }
673  else if ( unlabeled )
674  {
675  if ( mFeatStartId[i] < static_cast< int >( mLabelPositions.size() ) )
676  unlabeled->push_back( mLabelPositions[ mFeatStartId[i] ].get() );
677  }
678  }
679 
680  // unlabeled features also include those with no candidates
681  if ( unlabeled )
682  {
683  for ( const std::unique_ptr< LabelPosition > &position : mPositionsWithNoCandidates )
684  unlabeled->append( position.get() );
685  }
686 
687  return solList;
688 }
689 
690 void Problem::solution_cost()
691 {
692  mSol.totalCost = 0.0;
693 
694  LabelPosition *lp = nullptr;
695 
696  double amin[2];
697  double amax[2];
698 
699  for ( std::size_t i = 0; i < mFeatureCount; i++ )
700  {
701  if ( mSol.activeLabelIds[i] == -1 )
702  {
703  mSol.totalCost += mInactiveCost[i];
704  }
705  else
706  {
707  lp = mLabelPositions[ mSol.activeLabelIds[i] ].get();
708 
709  lp->getBoundingBox( amin, amax );
710  mActiveCandidatesIndex.intersects( QgsRectangle( amin[0], amin[1], amax[0], amax[1] ), [&lp, this]( const LabelPosition * lp2 )->bool
711  {
712  if ( lp->isInConflict( lp2 ) )
713  {
714  mSol.totalCost += mInactiveCost[lp2->getProblemFeatureId()] + lp2->cost();
715  }
716 
717  return true;
718  } );
719 
720  mSol.totalCost += lp->cost();
721  }
722  }
723 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
void reduce()
Definition: problem.cpp:66
bool isIn(int key)
int * label
Definition: problem.h:60
void getBoundingBox(double amin[2], double amax[2]) const
Returns bounding box - amin: xmin,ymin - amax: xmax,ymax.
int old_label
Definition: util.h:69
int feat
Definition: util.h:68
Thrown when something is added in a Full set.
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
int getProblemFeatureId() const
void remove(int key)
A rtree spatial index for use in the pal labeling engine.
Definition: palrtree.h:35
int * feat
Definition: problem.h:59
Problem(const QgsRectangle &extent)
Constructor for Problem.
Definition: problem.cpp:57
double cost() const
Returns the candidate label position&#39;s geographical cost.
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
void chain_search()
Test with very-large scale neighborhood.
Definition: problem.cpp:561
void decrementNumOverlaps()
Decreases the number of overlaps recorded against this position by 1.
double delta
Definition: problem.h:58
int degree
Definition: problem.h:57
void insert(int key, double p)
int getId() const
Returns the id.
void incrementNumOverlaps()
Increases the number of overlaps recorded against this position by 1.
int getNumOverlaps() const
void delete_chain(Chain *chain)
Definition: problem.cpp:47
void ignoreLabel(const LabelPosition *lp, PriorityQueue &list, PalRtree< LabelPosition > &candidatesIndex)
Definition: problem.cpp:140
#define EPSILON
Definition: util.h:78
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
int new_label
Definition: util.h:70
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:656
void removeFromIndex(PalRtree< LabelPosition > &index)
Removes the label position from the specified index.
void insertIntoIndex(PalRtree< LabelPosition > &index)
Inserts the label position into the specified index.
void init_sol_falp()
Definition: problem.cpp:163
void decreaseKey(int key)