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