QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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( mObstacleParts );
70 
71  mMutex.unlock();
72 }
73 
74 void Layer::setPriority( double priority )
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> biggestPart;
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  const 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  const 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 = std::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->x, fpart->y ) )
147  {
148  continue;
149  }
150 
151  // is the feature well defined? TODO Check epsilon
152  const 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  biggestPart = std::move( fpart );
186  }
187  // don't add the feature part now, do it later
188  }
189  else
190  {
191  // feature part is ready!
192  addFeaturePart( std::move( fpart ), 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  if ( !geom )
206  {
207  QgsDebugMsg( QStringLiteral( "Obstacle geometry passed to PAL labeling engine could not be converted to GEOS! %1" ).arg( ( *it )->asWkt() ) );
208  continue;
209  }
210 
211  // ignore invalid geometries (e.g. polygons with self-intersecting rings)
212  if ( GEOSisValid_r( geosctxt, geom.get() ) != 1 ) // 0=invalid, 1=valid, 2=exception
213  {
214  // this shouldn't happen -- we have already checked this while registering the feature
215  QgsDebugMsg( QStringLiteral( "Obstacle geometry passed to PAL labeling engine is not valid! %1" ).arg( ( *it )->asWkt() ) );
216  continue;
217  }
218 
219  const int type = GEOSGeomTypeId_r( geosctxt, geom.get() );
220 
221  if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
222  {
224  }
225 
226  std::unique_ptr<FeaturePart> fpart = std::make_unique<FeaturePart>( lf, geom.get() );
227 
228  // ignore invalid geometries
229  if ( ( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
230  ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
231  {
232  continue;
233  }
234 
235  // polygons: reorder coordinates
236  if ( type == GEOS_POLYGON && !GeomFunction::reorderPolygon( fpart->x, fpart->y ) )
237  {
238  continue;
239  }
240 
241  mGeosObstacleGeometries.emplace_back( std::move( geom ) );
242 
243  // feature part is ready!
244  addObstaclePart( fpart.release() );
245  }
246  }
247 
248  locker.unlock();
249 
250  // if using only biggest parts...
251  if ( ( !lf->labelAllParts() || lf->hasFixedPosition() ) && biggestPart )
252  {
253  addFeaturePart( std::move( biggestPart ), lf->labelText() );
254  addedFeature = true;
255  }
256 
257  // add feature to layer if we have added something
258  if ( addedFeature )
259  {
260  mHashtable.insert( lf->id(), lf );
261  }
262 
263  return addedFeature; // true if we've added something
264 }
265 
266 
267 void Layer::addFeaturePart( std::unique_ptr<FeaturePart> fpart, const QString &labelText )
268 {
269  // add to hashtable with equally named feature parts
270  if ( mMergeLines && !labelText.isEmpty() )
271  {
272  mConnectedHashtable[ labelText ].append( fpart.get() );
273  }
274 
275  // add to list of layer's feature parts
276  mFeatureParts.emplace_back( std::move( fpart ) );
277 }
278 
280 {
281  // add to list of layer's feature parts
282  mObstacleParts.append( fpart );
283 }
284 
285 static FeaturePart *_findConnectedPart( FeaturePart *partCheck, const QVector<FeaturePart *> &otherParts )
286 {
287  // iterate in the rest of the parts with the same label
288  auto it = otherParts.constBegin();
289  while ( it != otherParts.constEnd() )
290  {
291  if ( partCheck->isConnected( *it ) )
292  {
293  // stop checking for other connected parts
294  return *it;
295  }
296  ++it;
297  }
298 
299  return nullptr; // no connected part found...
300 }
301 
303 {
304  // go through all label texts
305  int connectedFeaturesId = 0;
306  for ( auto it = mConnectedHashtable.constBegin(); it != mConnectedHashtable.constEnd(); ++it )
307  {
308  QVector<FeaturePart *> partsToMerge = it.value();
309 
310  // need to start with biggest parts first, to avoid merging in side branches before we've
311  // merged the whole of the longest parts of the joined network
312  std::sort( partsToMerge.begin(), partsToMerge.end(), []( FeaturePart * a, FeaturePart * b )
313  {
314  return a->length() > b->length();
315  } );
316 
317  // go one-by-one part, try to merge
318  while ( partsToMerge.count() > 1 )
319  {
320  connectedFeaturesId++;
321 
322  // part we'll be checking against other in this round
323  FeaturePart *partToJoinTo = partsToMerge.takeFirst();
324  mConnectedFeaturesIds.insert( partToJoinTo->featureId(), connectedFeaturesId );
325 
326  // loop through all other parts
327  QVector< FeaturePart *> partsLeftToTryThisRound = partsToMerge;
328  while ( !partsLeftToTryThisRound.empty() )
329  {
330  if ( FeaturePart *otherPart = _findConnectedPart( partToJoinTo, partsLeftToTryThisRound ) )
331  {
332  partsLeftToTryThisRound.removeOne( otherPart );
333  if ( partToJoinTo->mergeWithFeaturePart( otherPart ) )
334  {
335  mConnectedFeaturesIds.insert( otherPart->featureId(), connectedFeaturesId );
336 
337  // otherPart was merged into partToJoinTo, so now we completely delete the redundant feature part which was merged in
338  partsToMerge.removeAll( otherPart );
339  const auto matchingPartIt = std::find_if( mFeatureParts.begin(), mFeatureParts.end(), [otherPart]( const std::unique_ptr< FeaturePart> &part ) { return part.get() == otherPart; } );
340  Q_ASSERT( matchingPartIt != mFeatureParts.end() );
341  mFeatureParts.erase( matchingPartIt );
342  }
343  }
344  else
345  {
346  // no candidate parts remain which we could possibly merge in
347  break;
348  }
349  }
350  }
351  }
352  mConnectedHashtable.clear();
353 
354  // Expunge feature parts that are smaller than the minimum size required
355  mFeatureParts.erase( std::remove_if( mFeatureParts.begin(), mFeatureParts.end(), []( const std::unique_ptr< FeaturePart > &part )
356  {
357  if ( part->feature()->minimumSize() != 0.0 && part->length() < part->feature()->minimumSize() )
358  {
359  return true;
360  }
361  return false;
362  } ), mFeatureParts.end() );
363 }
364 
365 int Layer::connectedFeatureId( QgsFeatureId featureId ) const
366 {
367  return mConnectedFeaturesIds.value( featureId, -1 );
368 }
369 
371 {
372  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
373  std::deque< std::unique_ptr< FeaturePart > > newFeatureParts;
374  while ( !mFeatureParts.empty() )
375  {
376  std::unique_ptr< FeaturePart > fpart = std::move( mFeatureParts.front() );
377  mFeatureParts.pop_front();
378 
379  const GEOSGeometry *geom = fpart->geos();
380  double chopInterval = fpart->repeatDistance();
381 
382  // whether we CAN chop
383  bool canChop = false;
384  double featureLen = 0;
385  if ( chopInterval != 0. && GEOSGeomTypeId_r( geosctxt, geom ) == GEOS_LINESTRING )
386  {
387  featureLen = fpart->length();
388  if ( featureLen > chopInterval )
389  canChop = true;
390  }
391 
392  // whether we SHOULD chop
393  bool shouldChop = canChop;
394  int possibleSegments = 0;
395  if ( canChop )
396  {
397  // never chop into segments smaller than required for the actual label text
398  chopInterval *= std::ceil( fpart->getLabelWidth() / fpart->repeatDistance() );
399 
400  // now work out how many full segments we could chop this line into
401  possibleSegments = static_cast< int >( std::floor( featureLen / chopInterval ) );
402 
403  // ... and use this to work out the actual chop distance for this line. Otherwise, we risk the
404  // situation of:
405  // 1. Line length of 3cm
406  // 2. Repeat distance of 2cm
407  // 3. Label size is 1.5 cm
408  //
409  // 2cm 1cm
410  // /--Label--/----/
411  //
412  // i.e. the labels would be off center and gravitate toward line starts
413  chopInterval = featureLen / possibleSegments;
414 
415  shouldChop = possibleSegments > 1;
416  }
417 
418  if ( shouldChop )
419  {
420  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
421 
422  // get number of points
423  unsigned int n;
424  GEOSCoordSeq_getSize_r( geosctxt, cs, &n );
425 
426  // Read points
427  std::vector<Point> points( n );
428  for ( unsigned int i = 0; i < n; ++i )
429  {
430 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
431  GEOSCoordSeq_getXY_r( geosctxt, cs, i, &points[i].x, &points[i].y );
432 #else
433  GEOSCoordSeq_getX_r( geosctxt, cs, i, &points[i].x );
434  GEOSCoordSeq_getY_r( geosctxt, cs, i, &points[i].y );
435 #endif
436  }
437 
438  // Cumulative length vector
439  std::vector<double> len( n, 0 );
440  for ( unsigned int i = 1; i < n; ++i )
441  {
442  const double dx = points[i].x - points[i - 1].x;
443  const double dy = points[i].y - points[i - 1].y;
444  len[i] = len[i - 1] + std::sqrt( dx * dx + dy * dy );
445  }
446 
447  // Walk along line
448  unsigned int cur = 0;
449  double lambda = 0;
450  std::vector<Point> part;
451 
452  QList<FeaturePart *> repeatParts;
453  repeatParts.reserve( possibleSegments );
454 
455  for ( int segment = 0; segment < possibleSegments; segment++ )
456  {
457  lambda += chopInterval;
458  for ( ; cur < n && lambda > len[cur]; ++cur )
459  {
460  part.push_back( points[cur] );
461  }
462  if ( cur >= n )
463  {
464  // Create final part
465  GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
466  for ( unsigned int i = 0; i < part.size(); ++i )
467  {
468 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
469  GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
470 #else
471  GEOSCoordSeq_setX_r( geosctxt, cooSeq, i, part[i].x );
472  GEOSCoordSeq_setY_r( geosctxt, cooSeq, i, part[i].y );
473 #endif
474  }
475  GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
476  std::unique_ptr< FeaturePart > newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
477  repeatParts.push_back( newfpart.get() );
478  newFeatureParts.emplace_back( std::move( newfpart ) );
479  break;
480  }
481  const double c = ( lambda - len[cur - 1] ) / ( len[cur] - len[cur - 1] );
482  Point p;
483  p.x = points[cur - 1].x + c * ( points[cur].x - points[cur - 1].x );
484  p.y = points[cur - 1].y + c * ( points[cur].y - points[cur - 1].y );
485  part.push_back( p );
486  GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
487  for ( std::size_t i = 0; i < part.size(); ++i )
488  {
489 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
490  GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
491 #else
492  GEOSCoordSeq_setX_r( geosctxt, cooSeq, static_cast< unsigned int >( i ), part[i].x );
493  GEOSCoordSeq_setY_r( geosctxt, cooSeq, static_cast< unsigned int >( i ), part[i].y );
494 #endif
495  }
496 
497  GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
498  std::unique_ptr< FeaturePart > newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
499  repeatParts.push_back( newfpart.get() );
500  newFeatureParts.emplace_back( std::move( newfpart ) );
501  part.clear();
502  part.push_back( p );
503  }
504 
505  for ( FeaturePart *partPtr : repeatParts )
506  partPtr->setTotalRepeats( repeatParts.count() );
507  }
508  else
509  {
510  newFeatureParts.emplace_back( std::move( fpart ) );
511  }
512  }
513 
514  mFeatureParts = std::move( newFeatureParts );
515 }
516 
517 
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:125
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:127
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:181
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:3222
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:65
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:161
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged).
Definition: feature.cpp:2159
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition: feature.cpp:2105
static bool reorderPolygon(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
std::deque< std::unique_ptr< FeaturePart > > mFeatureParts
List of feature parts.
Definition: layer.h:325
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:84
bool mLabelLayer
Definition: layer.h:338
void addFeaturePart(std::unique_ptr< FeaturePart > fpart, const QString &labelText=QString())
Add newly created feature part into r tree and to the list.
Definition: layer.cpp:267
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:302
QHash< QgsFeatureId, QgsLabelFeature * > mHashtable
Lookup table of label features (owned by the label feature provider that created them)
Definition: layer.h:350
void chopFeaturesAtRepeatDistance()
Chop layer features at the repeat distance.
Definition: layer.cpp:370
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:279
void setPriority(double priority)
Sets the layer's priority.
Definition: layer.cpp:74
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
QLineF segment(int index, QRectF rect, double radius)
double y
Definition: util.h:75
double x
Definition: util.h:75