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