QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
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 "layer.h"
31
32#include <cmath>
33#include <vector>
34
35#include "feature.h"
36#include "geomfunction.h"
37#include "internalexception.h"
38#include "pal.h"
39#include "qgslabelingengine.h"
40#include "qgslogger.h"
41#include "util.h"
42
43using namespace pal;
44
45Layer::Layer( QgsAbstractLabelProvider *provider, const QString &name, Qgis::LabelPlacement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal )
47 , mName( name )
48 , mPal( pal )
49 , mActive( active )
50 , mLabelLayer( toLabel )
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
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 = QgsGeosContext::get();
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 auto 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 QgsDebugError( 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 QgsDebugError( 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 auto 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
263void 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
281static 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
362{
363 return mConnectedFeaturesIds.value( featureId, -1 );
364}
365
367{
368 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
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 GEOSCoordSeq_getXY_r( geosctxt, cs, i, &points[i].x, &points[i].y );
427 }
428
429 // Cumulative length vector
430 std::vector<double> len( n, 0 );
431 for ( unsigned int i = 1; i < n; ++i )
432 {
433 const double dx = points[i].x - points[i - 1].x;
434 const double dy = points[i].y - points[i - 1].y;
435 len[i] = len[i - 1] + std::sqrt( dx * dx + dy * dy );
436 }
437
438 // Walk along line
439 unsigned int cur = 0;
440 double lambda = 0;
441 std::vector<Point> part;
442
443 QList<FeaturePart *> repeatParts;
444 repeatParts.reserve( possibleSegments );
445
446 for ( int segment = 0; segment < possibleSegments; segment++ )
447 {
448 lambda += chopInterval;
449 for ( ; cur < n && lambda > len[cur]; ++cur )
450 {
451 part.push_back( points[cur] );
452 }
453 if ( cur >= n )
454 {
455 // Create final part
456 GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
457 for ( unsigned int i = 0; i < part.size(); ++i )
458 {
459 GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
460 }
461 GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
462 auto newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
463 repeatParts.push_back( newfpart.get() );
464 newFeatureParts.emplace_back( std::move( newfpart ) );
465 break;
466 }
467 const double c = ( lambda - len[cur - 1] ) / ( len[cur] - len[cur - 1] );
468 Point p;
469 p.x = points[cur - 1].x + c * ( points[cur].x - points[cur - 1].x );
470 p.y = points[cur - 1].y + c * ( points[cur].y - points[cur - 1].y );
471 part.push_back( p );
472 GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
473 for ( std::size_t i = 0; i < part.size(); ++i )
474 {
475 GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
476 }
477
478 GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
479 auto newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
480 repeatParts.push_back( newfpart.get() );
481 newFeatureParts.emplace_back( std::move( newfpart ) );
482 part.clear();
483 part.push_back( p );
484 }
485
486 // cppcheck-suppress invalidLifetime
487 for ( FeaturePart *partPtr : repeatParts )
488 {
489 // cppcheck-suppress invalidLifetime
490 partPtr->setTotalRepeats( repeatParts.count() );
491 }
492 }
493 else
494 {
495 newFeatureParts.emplace_back( std::move( fpart ) );
496 }
497 }
498
499 mFeatureParts = std::move( newFeatureParts );
500}
501
502
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1194
An abstract interface class for label providers.
A generic rtree spatial index based on a libspatialindex backend.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
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 GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:260
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.
GEOSGeometry * geometry() const
Gets access to the associated geometry.
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.
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.
Represents a part of a label feature.
Definition feature.h:60
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition feature.cpp:167
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged).
Definition feature.cpp:2440
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition feature.cpp:2388
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:346
QHash< QString, QVector< FeaturePart * > > mConnectedHashtable
Definition layer.h:343
std::deque< std::unique_ptr< FeaturePart > > mFeatureParts
List of feature parts.
Definition layer.h:317
virtual ~Layer()
Definition layer.cpp:61
QList< FeaturePart * > mObstacleParts
List of obstacle parts.
Definition layer.h:320
double mDefaultPriority
Definition layer.h:326
friend class Pal
Definition layer.h:65
bool registerFeature(QgsLabelFeature *label)
Register a feature in the layer.
Definition layer.cpp:80
QString name() const
Returns the layer's name.
Definition layer.h:163
bool mLabelLayer
Definition layer.h:330
QgsAbstractLabelProvider * provider() const
Returns pointer to the associated provider.
Definition layer.h:158
bool active() const
Returns whether the layer is currently active.
Definition layer.h:199
Pal * mPal
Definition layer.h:324
QgsAbstractLabelProvider * mProvider
Definition layer.h:313
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:361
Qgis::LabelPlacement arrangement() const
Returns the layer's arrangement policy.
Definition layer.h:169
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:344
bool mMergeLines
Definition layer.h:336
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:341
void chopFeaturesAtRepeatDistance()
Chop layer features at the repeat distance.
Definition layer.cpp:366
std::vector< geos::unique_ptr > mGeosObstacleGeometries
Definition layer.h:322
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
QString mName
Definition layer.h:314
Qgis::LabelPlacement mArrangement
Optional flags used for some placement methods.
Definition layer.h:334
friend class FeaturePart
Definition layer.h:66
double priority() const
Returns the layer's priority, between 0 and 1.
Definition layer.h:244
bool mActive
Definition layer.h:329
static QLinkedList< const GEOSGeometry * > * unmulti(const GEOSGeometry *the_geom)
Definition util.cpp:37
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
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
#define QgsDebugError(str)
Definition qgslogger.h:57
QLineF segment(int index, QRectF rect, double radius)
double y
Definition util.h:79
double x
Definition util.h:79
struct GEOSGeom_t GEOSGeometry
Definition util.h:42