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