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