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