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