QGIS API Documentation 3.99.0-Master (e9821da5c6b)
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
43#include <QString>
44
45using namespace Qt::StringLiterals;
46
47using namespace pal;
48
49Layer::Layer( QgsAbstractLabelProvider *provider, const QString &name, Qgis::LabelPlacement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal )
51 , mName( name )
52 , mPal( pal )
53 , mActive( active )
54 , mLabelLayer( toLabel )
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
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( qMakePair( lf->id(), lf->subPartId() ) ) )
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 const std::optional<QVector<const GEOSGeometry *>> simpleGeometries = Util::unmulti( lf->geometry() );
110 if ( !simpleGeometries.has_value() ) // unmulti() failed?
111 {
113 }
114
115 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
116
117 const bool featureGeomIsObstacleGeom = lf->obstacleSettings().obstacleGeometry().isNull();
118
119 for ( const GEOSGeometry *geom : simpleGeometries.value() )
120 {
121 // ignore invalid geometries (e.g. polygons with self-intersecting rings)
122 if ( GEOSisValid_r( geosctxt, geom ) != 1 ) // 0=invalid, 1=valid, 2=exception
123 {
124 continue;
125 }
126
127 const int type = GEOSGeomTypeId_r( geosctxt, geom );
128
129 if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
130 {
132 }
133
134 auto fpart = std::make_unique<FeaturePart>( lf, geom );
135
136 // ignore invalid geometries
137 if ( ( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
138 ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
139 {
140 continue;
141 }
142
143 // polygons: reorder coordinates
144 if ( type == GEOS_POLYGON && !GeomFunction::reorderPolygon( fpart->x, fpart->y ) )
145 {
146 continue;
147 }
148
149 // is the feature well defined? TODO Check epsilon
150 const bool labelWellDefined = ( lf->size().width() > 0.0000001 && lf->size().height() > 0.0000001 );
151
152 if ( lf->obstacleSettings().isObstacle() && featureGeomIsObstacleGeom )
153 {
154 //if we are not labeling the layer, only insert it into the obstacle list and avoid an
155 //unnecessary copy
156 if ( mLabelLayer && labelWellDefined )
157 {
158 addObstaclePart( new FeaturePart( *fpart ) );
159 }
160 else
161 {
162 addObstaclePart( fpart.release() );
163 }
164 }
165
166 // feature has to be labeled?
167 if ( !mLabelLayer || !labelWellDefined )
168 {
169 //nothing more to do for this part
170 continue;
171 }
172
173 switch ( lf->multiPartBehavior() )
174 {
176 if ( 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 biggestPart = std::move( fpart );
187 }
188 // don't add the feature part now, do it later
189 break;
190 }
191 // fallthrough to default for point geometries
192 [[fallthrough]];
193
196 {
197 // feature part is ready!
198 addFeaturePart( std::move( fpart ), lf->labelText() );
199 addedFeature = true;
200 break;
201 }
202 }
203 }
204
205 if ( lf->obstacleSettings().isObstacle() && !featureGeomIsObstacleGeom )
206 {
207 //do the same for the obstacle geometry
208 const QgsGeometry obstacleGeometry = lf->obstacleSettings().obstacleGeometry();
209 for ( auto it = obstacleGeometry.const_parts_begin(); it != obstacleGeometry.const_parts_end(); ++it )
210 {
211 geos::unique_ptr geom = QgsGeos::asGeos( *it );
212
213 if ( !geom )
214 {
215 QgsDebugError( u"Obstacle geometry passed to PAL labeling engine could not be converted to GEOS! %1"_s.arg( ( *it )->asWkt() ) );
216 continue;
217 }
218
219 // ignore invalid geometries (e.g. polygons with self-intersecting rings)
220 if ( GEOSisValid_r( geosctxt, geom.get() ) != 1 ) // 0=invalid, 1=valid, 2=exception
221 {
222 // this shouldn't happen -- we have already checked this while registering the feature
223 QgsDebugError( u"Obstacle geometry passed to PAL labeling engine is not valid! %1"_s.arg( ( *it )->asWkt() ) );
224 continue;
225 }
226
227 const int type = GEOSGeomTypeId_r( geosctxt, geom.get() );
228
229 if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
230 {
232 }
233
234 auto fpart = std::make_unique<FeaturePart>( lf, geom.get() );
235
236 // ignore invalid geometries
237 if ( ( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
238 ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
239 {
240 continue;
241 }
242
243 // polygons: reorder coordinates
244 if ( type == GEOS_POLYGON && !GeomFunction::reorderPolygon( fpart->x, fpart->y ) )
245 {
246 continue;
247 }
248
249 mGeosObstacleGeometries.emplace_back( std::move( geom ) );
250
251 // feature part is ready!
252 addObstaclePart( fpart.release() );
253 }
254 }
255
256 locker.unlock();
257
258 // if using only biggest parts...
260 {
261 addFeaturePart( std::move( biggestPart ), lf->labelText() );
262 addedFeature = true;
263 }
264
265 // add feature to layer if we have added something
266 if ( addedFeature )
267 {
268 mHashtable.insert( qMakePair( lf->id(), lf->subPartId() ), lf );
269 }
270
271 return addedFeature; // true if we've added something
272}
273
274
275void Layer::addFeaturePart( std::unique_ptr<FeaturePart> fpart, const QString &labelText )
276{
277 // add to hashtable with equally named feature parts
278 if ( mMergeLines && !labelText.isEmpty() )
279 {
280 mConnectedHashtable[ labelText ].append( fpart.get() );
281 }
282
283 // add to list of layer's feature parts
284 mFeatureParts.emplace_back( std::move( fpart ) );
285}
286
288{
289 // add to list of layer's feature parts
290 mObstacleParts.append( fpart );
291}
292
293static FeaturePart *_findConnectedPart( FeaturePart *partCheck, const QVector<FeaturePart *> &otherParts )
294{
295 // iterate in the rest of the parts with the same label
296 auto it = otherParts.constBegin();
297 while ( it != otherParts.constEnd() )
298 {
299 if ( partCheck->isConnected( *it ) )
300 {
301 // stop checking for other connected parts
302 return *it;
303 }
304 ++it;
305 }
306
307 return nullptr; // no connected part found...
308}
309
311{
312 // go through all label texts
313 int connectedFeaturesId = 0;
314 for ( auto it = mConnectedHashtable.constBegin(); it != mConnectedHashtable.constEnd(); ++it )
315 {
316 QVector<FeaturePart *> partsToMerge = it.value();
317
318 // need to start with biggest parts first, to avoid merging in side branches before we've
319 // merged the whole of the longest parts of the joined network
320 std::sort( partsToMerge.begin(), partsToMerge.end(), []( FeaturePart * a, FeaturePart * b )
321 {
322 return a->length() > b->length();
323 } );
324
325 // go one-by-one part, try to merge
326 while ( partsToMerge.count() > 1 )
327 {
328 connectedFeaturesId++;
329
330 // part we'll be checking against other in this round
331 FeaturePart *partToJoinTo = partsToMerge.takeFirst();
332 mConnectedFeaturesIds.insert( partToJoinTo->featureId(), connectedFeaturesId );
333
334 // loop through all other parts
335 QVector< FeaturePart *> partsLeftToTryThisRound = partsToMerge;
336 while ( !partsLeftToTryThisRound.empty() )
337 {
338 if ( FeaturePart *otherPart = _findConnectedPart( partToJoinTo, partsLeftToTryThisRound ) )
339 {
340 partsLeftToTryThisRound.removeOne( otherPart );
341 if ( partToJoinTo->mergeWithFeaturePart( otherPart ) )
342 {
343 mConnectedFeaturesIds.insert( otherPart->featureId(), connectedFeaturesId );
344
345 // otherPart was merged into partToJoinTo, so now we completely delete the redundant feature part which was merged in
346 partsToMerge.removeAll( otherPart );
347 const auto matchingPartIt = std::find_if( mFeatureParts.begin(), mFeatureParts.end(), [otherPart]( const std::unique_ptr< FeaturePart> &part ) { return part.get() == otherPart; } );
348 Q_ASSERT( matchingPartIt != mFeatureParts.end() );
349 mFeatureParts.erase( matchingPartIt );
350 }
351 }
352 else
353 {
354 // no candidate parts remain which we could possibly merge in
355 break;
356 }
357 }
358 }
359 }
360 mConnectedHashtable.clear();
361
362 // Expunge feature parts that are smaller than the minimum size required
363 mFeatureParts.erase( std::remove_if( mFeatureParts.begin(), mFeatureParts.end(), []( const std::unique_ptr< FeaturePart > &part )
364 {
365 if ( part->feature()->minimumSize() != 0.0 && part->length() < part->feature()->minimumSize() )
366 {
367 return true;
368 }
369 return false;
370 } ), mFeatureParts.end() );
371}
372
374{
375 return mConnectedFeaturesIds.value( featureId, -1 );
376}
377
379{
380 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
381 std::deque< std::unique_ptr< FeaturePart > > newFeatureParts;
382 while ( !mFeatureParts.empty() )
383 {
384 std::unique_ptr< FeaturePart > fpart = std::move( mFeatureParts.front() );
385 mFeatureParts.pop_front();
386
387 const GEOSGeometry *geom = fpart->geos();
388 double chopInterval = fpart->repeatDistance();
389
390 // whether we CAN chop
391 bool canChop = false;
392 double featureLen = 0;
393 if ( chopInterval != 0. && GEOSGeomTypeId_r( geosctxt, geom ) == GEOS_LINESTRING )
394 {
395 featureLen = fpart->length();
396 if ( featureLen > chopInterval )
397 canChop = true;
398 }
399
400 // whether we SHOULD chop
401 bool shouldChop = canChop;
402 int possibleSegments = 0;
403 if ( canChop )
404 {
405 // never chop into segments smaller than required for the actual label text
406 chopInterval *= std::ceil( fpart->getLabelWidth() / fpart->repeatDistance() );
407
408 // now work out how many full segments we could chop this line into
409 possibleSegments = static_cast< int >( std::floor( featureLen / chopInterval ) );
410
411 // ... and use this to work out the actual chop distance for this line. Otherwise, we risk the
412 // situation of:
413 // 1. Line length of 3cm
414 // 2. Repeat distance of 2cm
415 // 3. Label size is 1.5 cm
416 //
417 // 2cm 1cm
418 // /--Label--/----/
419 //
420 // i.e. the labels would be off center and gravitate toward line starts
421 chopInterval = featureLen / possibleSegments;
422
423 shouldChop = possibleSegments > 1;
424 }
425
426 if ( shouldChop )
427 {
428 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
429
430 // get number of points
431 unsigned int n;
432 GEOSCoordSeq_getSize_r( geosctxt, cs, &n );
433
434 // Read points
435 std::vector<Point> points( n );
436 for ( unsigned int i = 0; i < n; ++i )
437 {
438 GEOSCoordSeq_getXY_r( geosctxt, cs, i, &points[i].x, &points[i].y );
439 }
440
441 // Cumulative length vector
442 std::vector<double> len( n, 0 );
443 for ( unsigned int i = 1; i < n; ++i )
444 {
445 const double dx = points[i].x - points[i - 1].x;
446 const double dy = points[i].y - points[i - 1].y;
447 len[i] = len[i - 1] + std::sqrt( dx * dx + dy * dy );
448 }
449
450 // Walk along line
451 unsigned int cur = 0;
452 double lambda = 0;
453 std::vector<Point> part;
454
455 QList<FeaturePart *> repeatParts;
456 repeatParts.reserve( possibleSegments );
457
458 for ( int segment = 0; segment < possibleSegments; segment++ )
459 {
460 lambda += chopInterval;
461 for ( ; cur < n && lambda > len[cur]; ++cur )
462 {
463 part.push_back( points[cur] );
464 }
465 if ( cur >= n )
466 {
467 // Create final part
468 GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
469 for ( unsigned int i = 0; i < part.size(); ++i )
470 {
471 GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
472 }
473 GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
474 auto newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
475 repeatParts.push_back( newfpart.get() );
476 newFeatureParts.emplace_back( std::move( newfpart ) );
477 break;
478 }
479 const double c = ( lambda - len[cur - 1] ) / ( len[cur] - len[cur - 1] );
480 Point p;
481 p.x = points[cur - 1].x + c * ( points[cur].x - points[cur - 1].x );
482 p.y = points[cur - 1].y + c * ( points[cur].y - points[cur - 1].y );
483 part.push_back( p );
484 GEOSCoordSequence *cooSeq = GEOSCoordSeq_create_r( geosctxt, static_cast< unsigned int >( part.size() ), 2 );
485 for ( std::size_t i = 0; i < part.size(); ++i )
486 {
487 GEOSCoordSeq_setXY_r( geosctxt, cooSeq, i, part[i].x, part[i].y );
488 }
489
490 GEOSGeometry *newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
491 auto newfpart = std::make_unique< FeaturePart >( fpart->feature(), newgeom );
492 repeatParts.push_back( newfpart.get() );
493 newFeatureParts.emplace_back( std::move( newfpart ) );
494 part.clear();
495 part.push_back( p );
496 }
497
498 // cppcheck-suppress invalidLifetime
499 for ( FeaturePart *partPtr : repeatParts )
500 {
501 // cppcheck-suppress invalidLifetime
502 partPtr->setTotalRepeats( repeatParts.count() );
503 }
504 }
505 else
506 {
507 newFeatureParts.emplace_back( std::move( fpart ) );
508 }
509 }
510
511 mFeatureParts = std::move( newFeatureParts );
512}
513
514
@ LabelLargestPartOnly
Place a label only on the largest part from the geometry.
Definition qgis.h:1284
@ SplitLabelTextLinesOverParts
Splits the label text over the parts of the geometry, such that each consecutive part is labeled with...
Definition qgis.h:1286
@ LabelEveryPartWithEntireLabel
Place the (same) entire label over every part from the geometry.
Definition qgis.h:1285
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1225
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:256
Describes a feature that should be used within the labeling engine.
Qgis::MultiPartLabelingBehavior multiPartBehavior() const
Returns the multipart labeling behavior.
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.
int subPartId() const
Sub part identifier (for features which register multiple labels).
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:169
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged).
Definition feature.cpp:2643
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Definition feature.cpp:2591
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:65
QList< FeaturePart * > mObstacleParts
List of obstacle parts.
Definition layer.h:319
double mDefaultPriority
Definition layer.h:325
friend class Pal
Definition layer.h:64
bool registerFeature(QgsLabelFeature *label)
Register a feature in the layer.
Definition layer.cpp:84
QString name() const
Returns the layer's name.
Definition layer.h:162
bool mLabelLayer
Definition layer.h:329
QgsAbstractLabelProvider * provider() const
Returns pointer to the associated provider.
Definition layer.h:157
bool active() const
Returns whether the layer is currently active.
Definition layer.h:198
Pal * mPal
Definition layer.h:323
QgsAbstractLabelProvider * mProvider
Definition layer.h:312
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:373
Qgis::LabelPlacement arrangement() const
Returns the layer's arrangement policy.
Definition layer.h:168
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:275
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:49
void joinConnectedFeatures()
Join connected features with the same label text.
Definition layer.cpp:310
void chopFeaturesAtRepeatDistance()
Chop layer features at the repeat distance.
Definition layer.cpp:378
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:287
QHash< QPair< QgsFeatureId, int >, QgsLabelFeature * > mHashtable
Lookup table of label features (owned by the label feature provider that created them).
Definition layer.h:340
void setPriority(double priority)
Sets the layer's priority.
Definition layer.cpp:74
QString mName
Definition layer.h:313
Qgis::LabelPlacement mArrangement
Optional flags used for some placement methods.
Definition layer.h:333
friend class FeaturePart
Definition layer.h:65
double priority() const
Returns the layer's priority, between 0 and 1.
Definition layer.h:243
bool mActive
Definition layer.h:328
static std::optional< QVector< const GEOSGeometry * > > unmulti(const GEOSGeometry *the_geom)
Definition util.cpp:39
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:59
QLineF segment(int index, QRectF rect, double radius)
double y
Definition util.h:78
double x
Definition util.h:78
struct GEOSGeom_t GEOSGeometry
Definition util.h:41