QGIS API Documentation  3.4.15-Madeira (e83d02e274)
layer.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 "layer.h"
32 #include "palexception.h"
33 #include "internalexception.h"
34 #include "feature.h"
35 #include "geomfunction.h"
36 #include "util.h"
37 #include "qgslabelingengine.h"
38 
39 #include <cmath>
40 #include <vector>
41 
42 using namespace pal;
43 
44 Layer::Layer( QgsAbstractLabelProvider *provider, const QString &name, QgsPalLayerSettings::Placement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll )
45  : mProvider( provider )
46  , mName( name )
47  , pal( pal )
48  , mObstacleType( QgsPalLayerSettings::PolygonInterior )
49  , mActive( active )
50  , mLabelLayer( toLabel )
51  , mDisplayAll( displayAll )
52  , mCentroidInside( false )
53  , mArrangement( arrangement )
54  , mMode( LabelPerFeature )
55  , mMergeLines( false )
56  , mUpsidedownLabels( Upright )
57 {
58  mFeatureIndex = new RTree<FeaturePart *, double, 2, double>();
59  mObstacleIndex = new RTree<FeaturePart *, double, 2, double>();
60 
61  if ( defaultPriority < 0.0001 )
62  mDefaultPriority = 0.0001;
63  else if ( defaultPriority > 1.0 )
64  mDefaultPriority = 1.0;
65  else
66  mDefaultPriority = defaultPriority;
67 }
68 
70 {
71  mMutex.lock();
72 
73  qDeleteAll( mFeatureParts );
74  qDeleteAll( mObstacleParts );
75 
76  delete mFeatureIndex;
77  delete mObstacleIndex;
78 
79  mMutex.unlock();
80 }
81 
83 {
84  if ( priority >= 1.0 ) // low priority
85  mDefaultPriority = 1.0;
86  else if ( priority <= 0.0001 )
87  mDefaultPriority = 0.0001; // high priority
88  else
90 }
91 
93 {
94  if ( lf->size().width() < 0 || lf->size().height() < 0 )
95  return false;
96 
97  mMutex.lock();
98 
99  if ( mHashtable.contains( lf->id() ) )
100  {
101  mMutex.unlock();
102  //A feature with this id already exists. Don't throw an exception as sometimes,
103  //the same feature is added twice (dateline split with otf-reprojection)
104  return false;
105  }
106 
107  // assign label feature to this PAL layer
108  lf->setLayer( this );
109 
110  // Split MULTI GEOM and Collection in simple geometries
111 
112  bool addedFeature = false;
113 
114  double geom_size = -1, biggest_size = -1;
115  FeaturePart *biggest_part = nullptr;
116 
117  // break the (possibly multi-part) geometry into simple geometries
118  QLinkedList<const GEOSGeometry *> *simpleGeometries = Util::unmulti( lf->geometry() );
119  if ( !simpleGeometries ) // unmulti() failed?
120  {
121  mMutex.unlock();
123  }
124 
125  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
126 
127  bool featureGeomIsObstacleGeom = !lf->obstacleGeometry();
128 
129  while ( !simpleGeometries->isEmpty() )
130  {
131  const GEOSGeometry *geom = simpleGeometries->takeFirst();
132 
133  // ignore invalid geometries (e.g. polygons with self-intersecting rings)
134  if ( GEOSisValid_r( geosctxt, geom ) != 1 ) // 0=invalid, 1=valid, 2=exception
135  {
136  continue;
137  }
138 
139  int type = GEOSGeomTypeId_r( geosctxt, geom );
140 
141  if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
142  {
143  mMutex.unlock();
145  }
146 
147  FeaturePart *fpart = new FeaturePart( lf, geom );
148 
149  // ignore invalid geometries
150  if ( ( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
151  ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
152  {
153  delete fpart;
154  continue;
155  }
156 
157  // polygons: reorder coordinates
158  if ( type == GEOS_POLYGON && GeomFunction::reorderPolygon( fpart->nbPoints, fpart->x, fpart->y ) != 0 )
159  {
160  delete fpart;
161  continue;
162  }
163 
164  // is the feature well defined? TODO Check epsilon
165  bool labelWellDefined = ( lf->size().width() > 0.0000001 && lf->size().height() > 0.0000001 );
166 
167  if ( lf->isObstacle() && featureGeomIsObstacleGeom )
168  {
169  //if we are not labeling the layer, only insert it into the obstacle list and avoid an
170  //unnecessary copy
171  if ( mLabelLayer && labelWellDefined )
172  {
173  addObstaclePart( new FeaturePart( *fpart ) );
174  }
175  else
176  {
177  addObstaclePart( fpart );
178  fpart = nullptr;
179  }
180  }
181 
182  // feature has to be labeled?
183  if ( !mLabelLayer || !labelWellDefined )
184  {
185  //nothing more to do for this part
186  delete fpart;
187  continue;
188  }
189 
190  if ( mMode == LabelPerFeature && ( type == GEOS_POLYGON || type == GEOS_LINESTRING ) )
191  {
192  if ( type == GEOS_LINESTRING )
193  GEOSLength_r( geosctxt, geom, &geom_size );
194  else if ( type == GEOS_POLYGON )
195  GEOSArea_r( geosctxt, geom, &geom_size );
196 
197  if ( geom_size > biggest_size )
198  {
199  biggest_size = geom_size;
200  delete biggest_part; // safe with NULL part
201  biggest_part = fpart;
202  }
203  else
204  {
205  delete fpart;
206  }
207  continue; // don't add the feature part now, do it later
208  }
209 
210  // feature part is ready!
211  addFeaturePart( fpart, lf->labelText() );
212  addedFeature = true;
213  }
214  delete simpleGeometries;
215 
216  if ( lf->isObstacle() && !featureGeomIsObstacleGeom )
217  {
218  //do the same for the obstacle geometry
219  simpleGeometries = Util::unmulti( lf->obstacleGeometry() );
220  if ( !simpleGeometries ) // unmulti() failed?
221  {
222  mMutex.unlock();
224  }
225 
226  while ( !simpleGeometries->isEmpty() )
227  {
228  const GEOSGeometry *geom = simpleGeometries->takeFirst();
229 
230  // ignore invalid geometries (e.g. polygons with self-intersecting rings)
231  if ( GEOSisValid_r( geosctxt, geom ) != 1 ) // 0=invalid, 1=valid, 2=exception
232  {
233  continue;
234  }
235 
236  int type = GEOSGeomTypeId_r( geosctxt, geom );
237 
238  if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
239  {
240  mMutex.unlock();
242  }
243 
244  FeaturePart *fpart = new FeaturePart( lf, geom );
245 
246  // ignore invalid geometries
247  if ( ( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
248  ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
249  {
250  delete fpart;
251  continue;
252  }
253 
254  // polygons: reorder coordinates
255  if ( type == GEOS_POLYGON && GeomFunction::reorderPolygon( fpart->nbPoints, fpart->x, fpart->y ) != 0 )
256  {
257  delete fpart;
258  continue;
259  }
260 
261  // feature part is ready!
262  addObstaclePart( fpart );
263  }
264  delete simpleGeometries;
265  }
266 
267  mMutex.unlock();
268 
269  // if using only biggest parts...
270  if ( ( mMode == LabelPerFeature || lf->hasFixedPosition() ) && biggest_part )
271  {
272  addFeaturePart( biggest_part, lf->labelText() );
273  addedFeature = true;
274  }
275 
276  // add feature to layer if we have added something
277  if ( addedFeature )
278  {
279  mHashtable.insert( lf->id(), lf );
280  }
281 
282  return addedFeature; // true if we've added something
283 }
284 
285 
286 void Layer::addFeaturePart( FeaturePart *fpart, const QString &labelText )
287 {
288  double bmin[2];
289  double bmax[2];
290  fpart->getBoundingBox( bmin, bmax );
291 
292  // add to list of layer's feature parts
293  mFeatureParts << fpart;
294 
295  // add to r-tree for fast spatial access
296  mFeatureIndex->Insert( bmin, bmax, fpart );
297 
298  // add to hashtable with equally named feature parts
299  if ( mMergeLines && !labelText.isEmpty() )
300  {
301  mConnectedHashtable[ labelText ].append( fpart );
302  }
303 }
304 
306 {
307  double bmin[2];
308  double bmax[2];
309  fpart->getBoundingBox( bmin, bmax );
310 
311  // add to list of layer's feature parts
312  mObstacleParts.append( fpart );
313 
314  // add to obstacle r-tree
315  mObstacleIndex->Insert( bmin, bmax, fpart );
316 }
317 
318 static FeaturePart *_findConnectedPart( FeaturePart *partCheck, const QVector<FeaturePart *> &otherParts )
319 {
320  // iterate in the rest of the parts with the same label
321  auto it = otherParts.constBegin();
322  while ( it != otherParts.constEnd() )
323  {
324  if ( partCheck->isConnected( *it ) )
325  {
326  // stop checking for other connected parts
327  return *it;
328  }
329  ++it;
330  }
331 
332  return nullptr; // no connected part found...
333 }
334 
336 {
337  // go through all label texts
338  int connectedFeaturesId = 0;
339  for ( auto it = mConnectedHashtable.constBegin(); it != mConnectedHashtable.constEnd(); ++it )
340  {
341  const QString labelTExt = it.key();
342  QVector<FeaturePart *> parts = it.value();
343  connectedFeaturesId++;
344 
345  // need to start with biggest parts first, to avoid merging in side branches before we've
346  // merged the whole of the longest parts of the joined network
347  std::sort( parts.begin(), parts.end(), []( FeaturePart * a, FeaturePart * b )
348  {
349  return a->length() > b->length();
350  } );
351 
352  // go one-by-one part, try to merge
353  while ( parts.count() > 1 )
354  {
355  // part we'll be checking against other in this round
356  FeaturePart *partCheck = parts.takeFirst();
357 
358  FeaturePart *otherPart = _findConnectedPart( partCheck, parts );
359  if ( otherPart )
360  {
361  // remove partCheck from r-tree
362  double checkpartBMin[2], checkpartBMax[2];
363  partCheck->getBoundingBox( checkpartBMin, checkpartBMax );
364 
365  double otherPartBMin[2], otherPartBMax[2];
366  otherPart->getBoundingBox( otherPartBMin, otherPartBMax );
367 
368  // merge points from partCheck to p->item
369  if ( otherPart->mergeWithFeaturePart( partCheck ) )
370  {
371  // remove the parts we are joining from the index
372  mFeatureIndex->Remove( checkpartBMin, checkpartBMax, partCheck );
373  mFeatureIndex->Remove( otherPartBMin, otherPartBMax, otherPart );
374 
375  // reinsert merged line to r-tree (probably not needed)
376  otherPart->getBoundingBox( otherPartBMin, otherPartBMax );
377  mFeatureIndex->Insert( otherPartBMin, otherPartBMax, otherPart );
378 
379  mConnectedFeaturesIds.insert( partCheck->featureId(), connectedFeaturesId );
380  mConnectedFeaturesIds.insert( otherPart->featureId(), connectedFeaturesId );
381 
382  mFeatureParts.removeOne( partCheck );
383  delete partCheck;
384  }
385  }
386  }
387  }
388 
389  mConnectedHashtable.clear();
390 }
391 
393 {
394  return mConnectedFeaturesIds.value( featureId, -1 );
395 }
396 
398 {
399  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
400  QLinkedList<FeaturePart *> newFeatureParts;
401  while ( !mFeatureParts.isEmpty() )
402  {
403  FeaturePart *fpart = mFeatureParts.takeFirst();
404  const GEOSGeometry *geom = fpart->geos();
405  double chopInterval = fpart->repeatDistance();
406  if ( chopInterval != 0. && GEOSGeomTypeId_r( geosctxt, geom ) == GEOS_LINESTRING )
407  {
408  chopInterval *= std::ceil( fpart->getLabelWidth() / fpart->repeatDistance() );
409 
410  double bmin[2], bmax[2];
411  fpart->getBoundingBox( bmin, bmax );
412  mFeatureIndex->Remove( bmin, bmax, fpart );
413 
414  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
415 
416  // get number of points
417  unsigned int n;
418  GEOSCoordSeq_getSize_r( geosctxt, cs, &n );
419 
420  // Read points
421  std::vector<Point> points( n );
422  for ( unsigned int i = 0; i < n; ++i )
423  {
424  GEOSCoordSeq_getX_r( geosctxt, cs, i, &points[i].x );
425  GEOSCoordSeq_getY_r( geosctxt, cs, i, &points[i].y );
426  }
427 
428  // Cumulative length vector
429  std::vector<double> len( n, 0 );
430  for ( unsigned int i = 1; i < n; ++i )
431  {
432  double dx = points[i].x - points[i - 1].x;
433  double dy = points[i].y - points[i - 1].y;
434  len[i] = len[i - 1] + std::sqrt( dx * dx + dy * dy );
435  }
436 
437  // Walk along line
438  unsigned int cur = 0;
439  double lambda = 0;
440  QVector<Point> part;
441  for ( ;; )
442  {
443  lambda += chopInterval;
444  for ( ; cur < n && lambda > len[cur]; ++cur )
445  {
446  part.push_back( points[cur] );
447  }
448  if ( cur >= n )
449  {
450  break;
451  }
452  double c = ( lambda - len[cur - 1] ) / ( len[cur] - len[cur - 1] );
453  Point p;
454  p.x = points[cur - 1].x + c * ( points[cur].x - points[cur - 1].x );
455  p.y = points[cur - 1].y + c * ( points[cur].y - points[cur - 1].y );
456  part.push_back( p );
457  GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, part.size(), 2 );
458  for ( int i = 0; i < part.size(); ++i )
459  {
460  GEOSCoordSeq_setX_r( geosctxt, cooSeq, i, part[i].x );
461  GEOSCoordSeq_setY_r( geosctxt, cooSeq, i, part[i].y );
462  }
463 
464  GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
465  FeaturePart *newfpart = new FeaturePart( fpart->feature(), newgeom );
466  newFeatureParts.append( newfpart );
467  newfpart->getBoundingBox( bmin, bmax );
468  mFeatureIndex->Insert( bmin, bmax, newfpart );
469  part.clear();
470  part.push_back( p );
471  }
472  // Create final part
473  part.push_back( points[n - 1] );
474  GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, part.size(), 2 );
475  for ( int i = 0; i < part.size(); ++i )
476  {
477  GEOSCoordSeq_setX_r( geosctxt, cooSeq, i, part[i].x );
478  GEOSCoordSeq_setY_r( geosctxt, cooSeq, i, part[i].y );
479  }
480 
481  GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
482  FeaturePart *newfpart = new FeaturePart( fpart->feature(), newgeom );
483  newFeatureParts.append( newfpart );
484  newfpart->getBoundingBox( bmin, bmax );
485  mFeatureIndex->Insert( bmin, bmax, newfpart );
486  delete fpart;
487  }
488  else
489  {
490  newFeatureParts.append( fpart );
491  }
492  }
493 
494  mFeatureParts = newFeatureParts;
495 }
QList< FeaturePart * > mObstacleParts
List of obstacle parts.
Definition: layer.h:266
QHash< QgsFeatureId, int > mConnectedFeaturesIds
Definition: layer.h:295
bool mMergeLines
Definition: layer.h:282
QgsLabelFeature * feature()
Returns the parent feature.
Definition: feature.h:118
bool mLabelLayer
Definition: layer.h:274
QHash< QgsFeatureId, QgsLabelFeature * > mHashtable
Lookup table of label features (owned by the label feature provider that created them) ...
Definition: layer.h:289
static QLinkedList< const GEOSGeometry * > * unmulti(const GEOSGeometry *the_geom)
Definition: util.cpp:87
virtual ~Layer()
Definition: layer.cpp:69
void setPriority(double priority)
Sets the layer&#39;s priority.
Definition: layer.cpp:82
const GEOSGeometry * geos() const
Returns the point set&#39;s GEOS geometry.
Definition: pointset.cpp:818
double mDefaultPriority
Definition: layer.h:270
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
Main Pal labeling class.
Definition: pal.h:87
void chopFeaturesAtRepeatDistance()
Chop layer features at the repeat distance *.
Definition: layer.cpp:397
double getLabelWidth() const
Definition: feature.h:244
double priority() const
Returns the layer&#39;s priority, between 0 and 1.
Definition: layer.h:177
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged)...
Definition: feature.cpp:1690
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:1674
bool isObstacle() const
Returns whether the feature will act as an obstacle for labels.
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2821
GEOSGeometry * geometry() const
Gets access to the associated geometry.
QHash< QString, QVector< FeaturePart * > > mConnectedHashtable
Definition: layer.h:294
QSizeF size() const
Size of the label (in map units)
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 hasFixedPosition() const
Whether the label should use a fixed position instead of being automatically placed.
RTree< FeaturePart *, double, 2, double, 8, 4 > * mFeatureIndex
Definition: layer.h:287
void joinConnectedFeatures()
Join connected features with the same label text.
Definition: layer.cpp:335
double * x
Definition: pointset.h:169
void addObstaclePart(FeaturePart *fpart)
Add newly created obstacle part into r tree and to the list.
Definition: layer.cpp:305
GEOSGeometry * obstacleGeometry() const
Returns the label&#39;s obstacle geometry, if different to the feature geometry.
void setLayer(pal::Layer *layer)
Assign PAL layer to the label feature. Should be only used internally in PAL.
Layer(QgsAbstractLabelProvider *provider, const QString &name, QgsPalLayerSettings::Placement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll=false)
Create a new layer.
Definition: layer.cpp:44
double x
Definition: util.h:72
double length() const
Returns length of line geometry.
Definition: pointset.cpp:826
Main class to handle feature.
Definition: feature.h:96
The QgsAbstractLabelProvider class is an interface class.
Thrown when a geometry type is not like expected.
double * y
Definition: pointset.h:170
void getBoundingBox(double min[2], double max[2]) const
Definition: pointset.h:126
bool registerFeature(QgsLabelFeature *label)
Register a feature in the layer.
Definition: layer.cpp:92
double y
Definition: util.h:72
Placement
Placement modes which determine how label candidates are generated for a feature. ...
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
int connectedFeatureId(QgsFeatureId featureId) const
Returns the connected feature ID for a label feature ID, which is unique for all features which have ...
Definition: layer.cpp:392
QLinkedList< FeaturePart * > mFeatureParts
List of feature parts.
Definition: layer.h:263
RTree< FeaturePart *, double, 2, double, 8, 4 > * mObstacleIndex
Definition: layer.h:292
QMutex mMutex
Definition: layer.h:297
double repeatDistance() const
Returns the distance between repeating labels for this feature.
Definition: feature.h:273
LabelMode mMode
Definition: layer.h:281
static int reorderPolygon(int nbPoints, double *x, double *y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside...
friend class FeaturePart
Definition: layer.h:66
QString labelText() const
Text of the label.
void addFeaturePart(FeaturePart *fpart, const QString &labelText=QString())
Add newly created feature part into r tree and to the list.
Definition: layer.cpp:286
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:151