Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgscompoundcurve.cpp
3 : : ----------------------
4 : : begin : September 2014
5 : : copyright : (C) 2014 by Marco Hugentobler
6 : : email : marco at sourcepole dot ch
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgscompoundcurve.h"
19 : : #include "qgsapplication.h"
20 : : #include "qgscircularstring.h"
21 : : #include "qgsgeometryutils.h"
22 : : #include "qgslinestring.h"
23 : : #include "qgswkbptr.h"
24 : : #include "qgsfeedback.h"
25 : :
26 : : #include <QJsonObject>
27 : : #include <QPainter>
28 : : #include <QPainterPath>
29 : : #include <memory>
30 : : #include <nlohmann/json.hpp>
31 : :
32 : 1127 : QgsCompoundCurve::QgsCompoundCurve()
33 : 2254 : {
34 : 1127 : mWkbType = QgsWkbTypes::CompoundCurve;
35 : 1127 : }
36 : :
37 : 581 : QgsCompoundCurve::~QgsCompoundCurve()
38 : 581 : {
39 : 326 : clear();
40 : 581 : }
41 : :
42 : 24 : bool QgsCompoundCurve::equals( const QgsCurve &other ) const
43 : : {
44 : 24 : const QgsCompoundCurve *otherCurve = qgsgeometry_cast< const QgsCompoundCurve * >( &other );
45 : 24 : if ( !otherCurve )
46 : 1 : return false;
47 : :
48 : 23 : if ( mWkbType != otherCurve->mWkbType )
49 : 2 : return false;
50 : :
51 : 21 : if ( mCurves.size() != otherCurve->mCurves.size() )
52 : 5 : return false;
53 : :
54 : 33 : for ( int i = 0; i < mCurves.size(); ++i )
55 : : {
56 : 23 : if ( *mCurves.at( i ) != *otherCurve->mCurves.at( i ) )
57 : 6 : return false;
58 : 17 : }
59 : :
60 : 10 : return true;
61 : 24 : }
62 : :
63 : 3 : QgsCompoundCurve *QgsCompoundCurve::createEmptyWithSameType() const
64 : : {
65 : 3 : auto result = std::make_unique< QgsCompoundCurve >();
66 : 3 : result->mWkbType = mWkbType;
67 : 3 : return result.release();
68 : 3 : }
69 : :
70 : 451 : QString QgsCompoundCurve::geometryType() const
71 : : {
72 : 902 : return QStringLiteral( "CompoundCurve" );
73 : : }
74 : :
75 : 5 : int QgsCompoundCurve::dimension() const
76 : : {
77 : 5 : return 1;
78 : : }
79 : :
80 : 6 : QgsCompoundCurve::QgsCompoundCurve( const QgsCompoundCurve &curve ): QgsCurve( curve )
81 : 12 : {
82 : 6 : mWkbType = curve.wkbType();
83 : 6 : mCurves.reserve( curve.mCurves.size() );
84 : 14 : for ( const QgsCurve *c : curve.mCurves )
85 : : {
86 : 8 : mCurves.append( c->clone() );
87 : : }
88 : 6 : }
89 : :
90 : 1 : QgsCompoundCurve &QgsCompoundCurve::operator=( const QgsCompoundCurve &curve )
91 : : {
92 : 1 : if ( &curve != this )
93 : : {
94 : 1 : clearCache();
95 : 1 : QgsCurve::operator=( curve );
96 : 3 : for ( const QgsCurve *c : curve.mCurves )
97 : : {
98 : 2 : mCurves.append( c->clone() );
99 : : }
100 : 1 : }
101 : 1 : return *this;
102 : : }
103 : :
104 : 6 : QgsCompoundCurve *QgsCompoundCurve::clone() const
105 : : {
106 : 6 : return new QgsCompoundCurve( *this );
107 : 0 : }
108 : :
109 : 821 : void QgsCompoundCurve::clear()
110 : : {
111 : 821 : mWkbType = QgsWkbTypes::CompoundCurve;
112 : 821 : qDeleteAll( mCurves );
113 : 821 : mCurves.clear();
114 : 821 : clearCache();
115 : 821 : }
116 : :
117 : 12 : QgsRectangle QgsCompoundCurve::calculateBoundingBox() const
118 : : {
119 : 12 : if ( mCurves.empty() )
120 : : {
121 : 2 : return QgsRectangle();
122 : : }
123 : :
124 : 10 : QgsRectangle bbox = mCurves.at( 0 )->boundingBox();
125 : 12 : for ( int i = 1; i < mCurves.size(); ++i )
126 : : {
127 : 2 : QgsRectangle curveBox = mCurves.at( i )->boundingBox();
128 : 2 : bbox.combineExtentWith( curveBox );
129 : 2 : }
130 : 10 : return bbox;
131 : 12 : }
132 : :
133 : 5 : bool QgsCompoundCurve::fromWkb( QgsConstWkbPtr &wkbPtr )
134 : : {
135 : 5 : clear();
136 : 5 : if ( !wkbPtr )
137 : : {
138 : 1 : return false;
139 : : }
140 : :
141 : 4 : QgsWkbTypes::Type type = wkbPtr.readHeader();
142 : 4 : if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CompoundCurve )
143 : : {
144 : 1 : return false;
145 : : }
146 : 3 : mWkbType = type;
147 : :
148 : : int nCurves;
149 : 3 : wkbPtr >> nCurves;
150 : 3 : QgsCurve *currentCurve = nullptr;
151 : 6 : for ( int i = 0; i < nCurves; ++i )
152 : : {
153 : 3 : QgsWkbTypes::Type curveType = wkbPtr.readHeader();
154 : 3 : wkbPtr -= 1 + sizeof( int );
155 : 3 : if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::LineString )
156 : : {
157 : 0 : currentCurve = new QgsLineString();
158 : 0 : }
159 : 3 : else if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::CircularString )
160 : : {
161 : 3 : currentCurve = new QgsCircularString();
162 : 3 : }
163 : : else
164 : : {
165 : 0 : return false;
166 : : }
167 : 3 : currentCurve->fromWkb( wkbPtr ); // also updates wkbPtr
168 : 3 : mCurves.append( currentCurve );
169 : 3 : }
170 : 3 : return true;
171 : 5 : }
172 : :
173 : 425 : bool QgsCompoundCurve::fromWkt( const QString &wkt )
174 : : {
175 : 425 : clear();
176 : :
177 : 425 : QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
178 : :
179 : 425 : if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::CompoundCurve )
180 : 1 : return false;
181 : 424 : mWkbType = parts.first;
182 : :
183 : 424 : QString secondWithoutParentheses = parts.second;
184 : 424 : secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
185 : 447 : if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
186 : 23 : secondWithoutParentheses.isEmpty() )
187 : 402 : return true;
188 : :
189 : 55 : QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
190 : :
191 : 22 : const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
192 : 60 : for ( const QString &childWkt : blocks )
193 : : {
194 : 40 : QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
195 : :
196 : 40 : if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::LineString )
197 : 20 : mCurves.append( new QgsLineString() );
198 : 20 : else if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::CircularString )
199 : 19 : mCurves.append( new QgsCircularString() );
200 : : else
201 : : {
202 : 1 : clear();
203 : 1 : return false;
204 : : }
205 : 39 : if ( !mCurves.back()->fromWkt( childWkt ) )
206 : : {
207 : 1 : clear();
208 : 1 : return false;
209 : : }
210 : 40 : }
211 : :
212 : : //scan through curves and check if dimensionality of curves is different to compound curve.
213 : : //if so, update the type dimensionality of the compound curve to match
214 : 20 : bool hasZ = false;
215 : 20 : bool hasM = false;
216 : 52 : for ( const QgsCurve *curve : std::as_const( mCurves ) )
217 : : {
218 : 35 : hasZ = hasZ || curve->is3D();
219 : 35 : hasM = hasM || curve->isMeasure();
220 : 35 : if ( hasZ && hasM )
221 : 3 : break;
222 : : }
223 : 20 : if ( hasZ )
224 : 6 : addZValue( 0 );
225 : 20 : if ( hasM )
226 : 5 : addMValue( 0 );
227 : :
228 : 20 : return true;
229 : 425 : }
230 : :
231 : 6 : int QgsCompoundCurve::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
232 : : {
233 : 6 : int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
234 : 12 : for ( const QgsCurve *curve : mCurves )
235 : : {
236 : 6 : binarySize += curve->wkbSize( flags );
237 : : }
238 : 6 : return binarySize;
239 : : }
240 : :
241 : 3 : QByteArray QgsCompoundCurve::asWkb( WkbFlags flags ) const
242 : : {
243 : 3 : QByteArray wkbArray;
244 : 3 : wkbArray.resize( QgsCompoundCurve::wkbSize( flags ) );
245 : 3 : QgsWkbPtr wkb( wkbArray );
246 : 3 : wkb << static_cast<char>( QgsApplication::endian() );
247 : 3 : wkb << static_cast<quint32>( wkbType() );
248 : 3 : wkb << static_cast<quint32>( mCurves.size() );
249 : 6 : for ( const QgsCurve *curve : mCurves )
250 : : {
251 : 3 : wkb << curve->asWkb( flags );
252 : : }
253 : 3 : return wkbArray;
254 : 3 : }
255 : :
256 : 443 : QString QgsCompoundCurve::asWkt( int precision ) const
257 : : {
258 : 443 : QString wkt = wktTypeStr();
259 : 443 : if ( isEmpty() )
260 : 402 : wkt += QLatin1String( " EMPTY" );
261 : : else
262 : : {
263 : 41 : wkt += QLatin1String( " (" );
264 : 108 : for ( const QgsCurve *curve : mCurves )
265 : : {
266 : 67 : QString childWkt = curve->asWkt( precision );
267 : 67 : if ( qgsgeometry_cast<const QgsLineString *>( curve ) )
268 : : {
269 : : // Type names of linear geometries are omitted
270 : 29 : childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
271 : 29 : }
272 : 67 : wkt += childWkt + ',';
273 : 67 : }
274 : 41 : if ( wkt.endsWith( ',' ) )
275 : : {
276 : 41 : wkt.chop( 1 );
277 : 41 : }
278 : 41 : wkt += ')';
279 : : }
280 : 443 : return wkt;
281 : 443 : }
282 : :
283 : 3 : QDomElement QgsCompoundCurve::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
284 : : {
285 : : // GML2 does not support curves
286 : 3 : std::unique_ptr< QgsLineString > line( curveToLine() );
287 : 3 : QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
288 : 3 : return gml;
289 : 3 : }
290 : :
291 : 3 : QDomElement QgsCompoundCurve::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
292 : : {
293 : 6 : QDomElement compoundCurveElem = doc.createElementNS( ns, QStringLiteral( "CompositeCurve" ) );
294 : :
295 : 3 : if ( isEmpty() )
296 : 1 : return compoundCurveElem;
297 : :
298 : 6 : for ( const QgsCurve *curve : mCurves )
299 : : {
300 : 8 : QDomElement curveMemberElem = doc.createElementNS( ns, QStringLiteral( "curveMember" ) );
301 : 4 : QDomElement curveElem = curve->asGml3( doc, precision, ns, axisOrder );
302 : 4 : curveMemberElem.appendChild( curveElem );
303 : 4 : compoundCurveElem.appendChild( curveMemberElem );
304 : 4 : }
305 : :
306 : 2 : return compoundCurveElem;
307 : 3 : }
308 : :
309 : 3 : json QgsCompoundCurve::asJsonObject( int precision ) const
310 : : {
311 : : // GeoJSON does not support curves
312 : 3 : std::unique_ptr< QgsLineString > line( curveToLine() );
313 : 3 : return line->asJsonObject( precision );
314 : 3 : }
315 : :
316 : 4 : double QgsCompoundCurve::length() const
317 : : {
318 : 4 : double length = 0;
319 : 10 : for ( const QgsCurve *curve : mCurves )
320 : : {
321 : 6 : length += curve->length();
322 : : }
323 : 4 : return length;
324 : : }
325 : :
326 : 22 : QgsPoint QgsCompoundCurve::startPoint() const
327 : : {
328 : 22 : if ( mCurves.empty() )
329 : : {
330 : 1 : return QgsPoint();
331 : : }
332 : 21 : return mCurves.at( 0 )->startPoint();
333 : 22 : }
334 : :
335 : 21 : QgsPoint QgsCompoundCurve::endPoint() const
336 : : {
337 : 21 : if ( mCurves.empty() )
338 : : {
339 : 1 : return QgsPoint();
340 : : }
341 : 20 : return mCurves.at( mCurves.size() - 1 )->endPoint();
342 : 21 : }
343 : :
344 : 34 : void QgsCompoundCurve::points( QgsPointSequence &pts ) const
345 : : {
346 : 34 : pts.clear();
347 : 34 : if ( mCurves.empty() )
348 : : {
349 : 5 : return;
350 : : }
351 : :
352 : 29 : mCurves[0]->points( pts );
353 : 46 : for ( int i = 1; i < mCurves.size(); ++i )
354 : : {
355 : 17 : QgsPointSequence pList;
356 : 17 : mCurves[i]->points( pList );
357 : 17 : pList.removeFirst(); //first vertex already added in previous line
358 : 17 : pts.append( pList );
359 : 17 : }
360 : 34 : }
361 : :
362 : 112 : int QgsCompoundCurve::numPoints() const
363 : : {
364 : 112 : int nPoints = 0;
365 : 112 : int nCurves = mCurves.size();
366 : 112 : if ( nCurves < 1 )
367 : : {
368 : 18 : return 0;
369 : : }
370 : :
371 : 211 : for ( int i = 0; i < nCurves; ++i )
372 : : {
373 : 117 : nPoints += mCurves.at( i )->numPoints() - 1; //last vertex is equal to first of next section
374 : 117 : }
375 : 94 : nPoints += 1; //last vertex was removed above
376 : 94 : return nPoints;
377 : 112 : }
378 : :
379 : 475 : bool QgsCompoundCurve::isEmpty() const
380 : : {
381 : 475 : if ( mCurves.isEmpty() )
382 : 418 : return true;
383 : :
384 : 57 : for ( QgsCurve *curve : mCurves )
385 : : {
386 : 57 : if ( !curve->isEmpty() )
387 : 57 : return false;
388 : : }
389 : 0 : return true;
390 : 475 : }
391 : :
392 : 4 : bool QgsCompoundCurve::isValid( QString &error, int flags ) const
393 : : {
394 : 4 : if ( mCurves.isEmpty() )
395 : 1 : return true;
396 : :
397 : 8 : for ( int i = 0; i < mCurves.size() ; ++i )
398 : : {
399 : 6 : if ( !mCurves[i]->isValid( error, flags ) )
400 : : {
401 : 1 : error = QObject::tr( "Curve[%1]: %2" ).arg( i + 1 ).arg( error );
402 : 1 : return false;
403 : : }
404 : 5 : }
405 : 2 : return QgsCurve::isValid( error, flags );
406 : 4 : }
407 : :
408 : 19 : QgsLineString *QgsCompoundCurve::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
409 : : {
410 : 19 : QgsLineString *line = new QgsLineString();
411 : 19 : std::unique_ptr< QgsLineString > currentLine;
412 : 45 : for ( const QgsCurve *curve : mCurves )
413 : : {
414 : 26 : currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
415 : 26 : line->append( currentLine.get() );
416 : : }
417 : 19 : return line;
418 : 19 : }
419 : :
420 : 1 : QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
421 : : {
422 : 1 : std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
423 : :
424 : 3 : for ( QgsCurve *curve : mCurves )
425 : : {
426 : 2 : std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) );
427 : 2 : if ( gridified )
428 : : {
429 : 2 : result->mCurves.append( gridified.release() );
430 : 2 : }
431 : 2 : }
432 : :
433 : 1 : if ( result->mCurves.empty() )
434 : 0 : return nullptr;
435 : : else
436 : 1 : return result.release();
437 : 1 : }
438 : :
439 : 11 : bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
440 : : {
441 : 11 : bool result = false;
442 : 11 : const QVector< QgsCurve * > curves = mCurves;
443 : 11 : int i = 0;
444 : 11 : QgsPoint lastEnd;
445 : 26 : for ( QgsCurve *curve : curves )
446 : : {
447 : 15 : result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
448 : 15 : if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
449 : : {
450 : : // empty curve, remove it
451 : 1 : delete mCurves.takeAt( i );
452 : 1 : result = true;
453 : 1 : }
454 : : else
455 : : {
456 : : // ensure this line starts exactly where previous line ended
457 : 14 : if ( i > 0 )
458 : : {
459 : 4 : curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
460 : 4 : }
461 : 14 : lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
462 : : }
463 : 15 : i++;
464 : : }
465 : 11 : return result;
466 : 11 : }
467 : :
468 : 0 : bool QgsCompoundCurve::boundingBoxIntersects( const QgsRectangle &rectangle ) const
469 : : {
470 : 0 : if ( mCurves.empty() )
471 : 0 : return false;
472 : :
473 : : // if we already have the bounding box calculated, then this check is trivial!
474 : 0 : if ( !mBoundingBox.isNull() )
475 : : {
476 : 0 : return mBoundingBox.intersects( rectangle );
477 : : }
478 : :
479 : : // otherwise loop through each member curve and test the bounding box intersection.
480 : : // This gives us a chance to use optimisations which may be present on the individual
481 : : // curve subclasses, and at worst it will cause a calculation of the bounding box
482 : : // of each individual member curve which we would have to do anyway... (and these
483 : : // bounding boxes are cached, so would be reused without additional expense)
484 : 0 : for ( const QgsCurve *curve : mCurves )
485 : : {
486 : 0 : if ( curve->boundingBoxIntersects( rectangle ) )
487 : 0 : return true;
488 : : }
489 : :
490 : : // even if we don't intersect the bounding box of any member curves, we may still intersect the
491 : : // bounding box of the overall compound curve.
492 : : // so here we fall back to the non-optimised base class check which has to first calculate
493 : : // the overall bounding box of the compound curve..
494 : 0 : return QgsAbstractGeometry::boundingBoxIntersects( rectangle );
495 : 0 : }
496 : :
497 : 35 : const QgsCurve *QgsCompoundCurve::curveAt( int i ) const
498 : : {
499 : 35 : if ( i < 0 || i >= mCurves.size() )
500 : : {
501 : 10 : return nullptr;
502 : : }
503 : 25 : return mCurves.at( i );
504 : 35 : }
505 : :
506 : 202 : void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
507 : : {
508 : 202 : if ( !c )
509 : 1 : return;
510 : :
511 : 201 : if ( mCurves.empty() )
512 : : {
513 : 127 : setZMTypeFromSubGeometry( c, QgsWkbTypes::CompoundCurve );
514 : 127 : }
515 : :
516 : 201 : if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
517 : : {
518 : 3 : c->addZValue();
519 : 3 : }
520 : 198 : else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
521 : : {
522 : 4 : c->dropZValue();
523 : 4 : }
524 : 201 : if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
525 : : {
526 : 3 : c->addMValue();
527 : 3 : }
528 : 198 : else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
529 : : {
530 : 4 : c->dropMValue();
531 : 4 : }
532 : :
533 : 201 : QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
534 : 201 : const QgsLineString *newLineString = qgsgeometry_cast< const QgsLineString * >( c );
535 : 201 : const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
536 : 201 : if ( canExtendPrevious )
537 : : {
538 : 2 : previousLineString->append( newLineString );
539 : : // we are taking ownership, so delete the input curve
540 : 2 : delete c;
541 : 2 : c = nullptr;
542 : 2 : }
543 : : else
544 : : {
545 : 199 : mCurves.append( c );
546 : : }
547 : :
548 : 201 : clearCache();
549 : 202 : }
550 : :
551 : 21 : void QgsCompoundCurve::removeCurve( int i )
552 : : {
553 : 21 : if ( i < 0 || i >= mCurves.size() )
554 : : {
555 : 5 : return;
556 : : }
557 : :
558 : 16 : delete mCurves.takeAt( i );
559 : 16 : clearCache();
560 : 21 : }
561 : :
562 : 9 : void QgsCompoundCurve::addVertex( const QgsPoint &pt )
563 : : {
564 : 9 : if ( mCurves.isEmpty() || mWkbType == QgsWkbTypes::Unknown )
565 : : {
566 : 6 : setZMTypeFromSubGeometry( &pt, QgsWkbTypes::CompoundCurve );
567 : 6 : }
568 : :
569 : : //is last curve QgsLineString
570 : 9 : QgsCurve *lastCurve = nullptr;
571 : 9 : if ( !mCurves.isEmpty() )
572 : : {
573 : 3 : lastCurve = mCurves.at( mCurves.size() - 1 );
574 : 3 : }
575 : :
576 : 9 : QgsLineString *line = nullptr;
577 : 9 : if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != QgsWkbTypes::LineString )
578 : : {
579 : 6 : line = new QgsLineString();
580 : 6 : mCurves.append( line );
581 : 6 : if ( lastCurve )
582 : : {
583 : 0 : line->addVertex( lastCurve->endPoint() );
584 : 0 : }
585 : 6 : lastCurve = line;
586 : 6 : }
587 : : else //create new QgsLineString* with point in it
588 : : {
589 : 3 : line = static_cast<QgsLineString *>( lastCurve );
590 : : }
591 : 9 : line->addVertex( pt );
592 : 9 : clearCache();
593 : 9 : }
594 : :
595 : 0 : void QgsCompoundCurve::draw( QPainter &p ) const
596 : : {
597 : 0 : for ( const QgsCurve *curve : mCurves )
598 : : {
599 : 0 : curve->draw( p );
600 : : }
601 : 0 : }
602 : :
603 : 3 : void QgsCompoundCurve::transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d, bool transformZ )
604 : : {
605 : 9 : for ( QgsCurve *curve : std::as_const( mCurves ) )
606 : : {
607 : 6 : curve->transform( ct, d, transformZ );
608 : : }
609 : 3 : clearCache();
610 : 3 : }
611 : :
612 : 1 : void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
613 : : {
614 : 3 : for ( QgsCurve *curve : std::as_const( mCurves ) )
615 : : {
616 : 2 : curve->transform( t, zTranslate, zScale, mTranslate, mScale );
617 : : }
618 : 1 : clearCache();
619 : 1 : }
620 : :
621 : 4 : void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
622 : : {
623 : 4 : QPainterPath pp;
624 : 8 : for ( const QgsCurve *curve : mCurves )
625 : : {
626 : 4 : curve->addToPainterPath( pp );
627 : : }
628 : 4 : path.addPath( pp );
629 : 4 : }
630 : :
631 : 0 : void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
632 : : {
633 : 0 : QPainterPath pp;
634 : 0 : for ( const QgsCurve *curve : mCurves )
635 : : {
636 : 0 : curve->addToPainterPath( pp );
637 : : }
638 : 0 : p.drawPath( pp );
639 : 0 : }
640 : :
641 : 13 : bool QgsCompoundCurve::insertVertex( QgsVertexId position, const QgsPoint &vertex )
642 : : {
643 : 13 : QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
644 : 13 : if ( curveIds.empty() )
645 : : {
646 : 3 : return false;
647 : : }
648 : 10 : int curveId = curveIds.at( 0 ).first;
649 : 10 : if ( curveId >= mCurves.size() )
650 : : {
651 : 0 : return false;
652 : : }
653 : :
654 : 10 : bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
655 : 10 : if ( success )
656 : : {
657 : 8 : clearCache(); //bbox changed
658 : 8 : }
659 : 10 : return success;
660 : 13 : }
661 : :
662 : 17 : bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
663 : : {
664 : 17 : QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
665 : 17 : QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
666 : 28 : for ( ; idIt != curveIds.constEnd(); ++idIt )
667 : : {
668 : 11 : mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
669 : 11 : }
670 : :
671 : 17 : bool success = !curveIds.isEmpty();
672 : 17 : if ( success )
673 : : {
674 : 11 : clearCache(); //bbox changed
675 : 11 : }
676 : 17 : return success;
677 : 17 : }
678 : :
679 : 9 : bool QgsCompoundCurve::deleteVertex( QgsVertexId position )
680 : : {
681 : 9 : QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
682 : 9 : if ( curveIds.size() == 1 )
683 : : {
684 : 4 : if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
685 : : {
686 : 0 : clearCache(); //bbox may have changed
687 : 0 : return false;
688 : : }
689 : 4 : if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 )
690 : : {
691 : 2 : removeCurve( curveIds.at( 0 ).first );
692 : 2 : }
693 : 4 : }
694 : 5 : else if ( curveIds.size() == 2 )
695 : : {
696 : : Q_ASSERT( curveIds.at( 1 ).first == curveIds.at( 0 ).first + 1 );
697 : : Q_ASSERT( curveIds.at( 0 ).second.vertex == mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 );
698 : : Q_ASSERT( curveIds.at( 1 ).second.vertex == 0 );
699 : 2 : QgsPoint startPoint = mCurves.at( curveIds.at( 0 ).first ) ->startPoint();
700 : 2 : QgsPoint endPoint = mCurves.at( curveIds.at( 1 ).first ) ->endPoint();
701 : 2 : if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::LineString &&
702 : 2 : QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
703 : 0 : mCurves.at( curveIds.at( 1 ).first )->numPoints() > 3 )
704 : : {
705 : 0 : QgsPoint intermediatePoint;
706 : : QgsVertexId::VertexType type;
707 : 0 : mCurves.at( curveIds.at( 1 ).first ) ->pointAt( 2, intermediatePoint, type );
708 : 0 : mCurves.at( curveIds.at( 0 ).first )->moveVertex(
709 : 0 : QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), intermediatePoint );
710 : 0 : }
711 : 2 : else if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
712 : : {
713 : 0 : clearCache(); //bbox may have changed
714 : 0 : return false;
715 : : }
716 : 2 : if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
717 : 0 : mCurves.at( curveIds.at( 0 ).first )->numPoints() > 0 &&
718 : 0 : QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::LineString )
719 : : {
720 : 0 : QgsPoint intermediatePoint = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
721 : 0 : mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), intermediatePoint );
722 : 0 : }
723 : 2 : else if ( !mCurves.at( curveIds.at( 1 ).first )->deleteVertex( curveIds.at( 1 ).second ) )
724 : : {
725 : 0 : clearCache(); //bbox may have changed
726 : 0 : return false;
727 : : }
728 : 3 : if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
729 : 1 : mCurves.at( curveIds.at( 1 ).first )->numPoints() != 0 )
730 : : {
731 : 1 : mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), startPoint );
732 : 1 : removeCurve( curveIds.at( 0 ).first );
733 : 1 : }
734 : 2 : else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() != 0 &&
735 : 1 : mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
736 : : {
737 : 2 : mCurves.at( curveIds.at( 0 ).first )->moveVertex(
738 : 1 : QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), endPoint );
739 : 1 : removeCurve( curveIds.at( 1 ).first );
740 : 1 : }
741 : 0 : else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
742 : 0 : mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
743 : : {
744 : 0 : removeCurve( curveIds.at( 1 ).first );
745 : 0 : removeCurve( curveIds.at( 0 ).first );
746 : 0 : QgsLineString *line = new QgsLineString();
747 : 0 : line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
748 : 0 : line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
749 : 0 : mCurves.insert( curveIds.at( 0 ).first, line );
750 : 0 : }
751 : : else
752 : : {
753 : 0 : QgsPoint endPointOfFirst = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
754 : 0 : QgsPoint startPointOfSecond = mCurves.at( curveIds.at( 1 ).first ) ->startPoint();
755 : 0 : if ( endPointOfFirst != startPointOfSecond )
756 : : {
757 : 0 : QgsLineString *line = new QgsLineString();
758 : 0 : line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
759 : 0 : line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
760 : 0 : mCurves.insert( curveIds.at( 1 ).first, line );
761 : 0 : }
762 : 0 : }
763 : 2 : }
764 : :
765 : 9 : bool success = !curveIds.isEmpty();
766 : 9 : if ( success )
767 : : {
768 : 6 : clearCache(); //bbox changed
769 : 6 : }
770 : 9 : return success;
771 : 9 : }
772 : :
773 : 121 : QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
774 : : {
775 : 121 : QVector< QPair<int, QgsVertexId> > curveIds;
776 : :
777 : 121 : int currentVertexIndex = 0;
778 : 154 : for ( int i = 0; i < mCurves.size(); ++i )
779 : : {
780 : 129 : int increment = mCurves.at( i )->numPoints() - 1;
781 : 129 : if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
782 : : {
783 : 94 : int curveVertexId = id.vertex - currentVertexIndex;
784 : 94 : QgsVertexId vid;
785 : 94 : vid.part = 0;
786 : 94 : vid.ring = 0;
787 : 94 : vid.vertex = curveVertexId;
788 : 94 : curveIds.append( qMakePair( i, vid ) );
789 : 94 : if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
790 : : {
791 : 9 : vid.vertex = 0;
792 : 9 : curveIds.append( qMakePair( i + 1, vid ) );
793 : 9 : }
794 : 94 : break;
795 : : }
796 : 35 : else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
797 : : {
798 : 2 : int curveVertexId = id.vertex - currentVertexIndex;
799 : 2 : QgsVertexId vid;
800 : 2 : vid.part = 0;
801 : 2 : vid.ring = 0;
802 : 2 : vid.vertex = curveVertexId;
803 : 2 : curveIds.append( qMakePair( i, vid ) );
804 : 2 : break;
805 : : }
806 : 33 : currentVertexIndex += increment;
807 : 33 : }
808 : :
809 : 121 : return curveIds;
810 : 121 : }
811 : :
812 : 23 : double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
813 : : {
814 : 23 : return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
815 : : }
816 : :
817 : 177 : bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
818 : : {
819 : 177 : int currentVertexId = 0;
820 : 205 : for ( int j = 0; j < mCurves.size(); ++j )
821 : : {
822 : 193 : int nCurvePoints = mCurves.at( j )->numPoints();
823 : 193 : if ( ( node - currentVertexId ) < nCurvePoints )
824 : : {
825 : 165 : return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
826 : : }
827 : 28 : currentVertexId += ( nCurvePoints - 1 );
828 : 28 : }
829 : 12 : return false;
830 : 177 : }
831 : :
832 : 22 : double QgsCompoundCurve::xAt( int index ) const
833 : : {
834 : 22 : int currentVertexId = 0;
835 : 30 : for ( int j = 0; j < mCurves.size(); ++j )
836 : : {
837 : 25 : int nCurvePoints = mCurves.at( j )->numPoints();
838 : 25 : if ( ( index - currentVertexId ) < nCurvePoints )
839 : : {
840 : 17 : return mCurves.at( j )->xAt( index - currentVertexId );
841 : : }
842 : 8 : currentVertexId += ( nCurvePoints - 1 );
843 : 8 : }
844 : 5 : return 0.0;
845 : 22 : }
846 : :
847 : 22 : double QgsCompoundCurve::yAt( int index ) const
848 : : {
849 : 22 : int currentVertexId = 0;
850 : 30 : for ( int j = 0; j < mCurves.size(); ++j )
851 : : {
852 : 25 : int nCurvePoints = mCurves.at( j )->numPoints();
853 : 25 : if ( ( index - currentVertexId ) < nCurvePoints )
854 : : {
855 : 17 : return mCurves.at( j )->yAt( index - currentVertexId );
856 : : }
857 : 8 : currentVertexId += ( nCurvePoints - 1 );
858 : 8 : }
859 : 5 : return 0.0;
860 : 22 : }
861 : :
862 : 4 : bool QgsCompoundCurve::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
863 : : {
864 : 4 : bool res = true;
865 : 7 : for ( QgsCurve *curve : std::as_const( mCurves ) )
866 : : {
867 : 4 : if ( !curve->transform( transformer ) )
868 : : {
869 : 1 : res = false;
870 : 1 : break;
871 : : }
872 : :
873 : 3 : if ( feedback && feedback->isCanceled() )
874 : : {
875 : 0 : res = false;
876 : 0 : break;
877 : : }
878 : : }
879 : 4 : clearCache();
880 : 4 : return res;
881 : : }
882 : :
883 : 3 : void QgsCompoundCurve::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
884 : : {
885 : 6 : for ( QgsCurve *curve : std::as_const( mCurves ) )
886 : : {
887 : 3 : curve->filterVertices( filter );
888 : : }
889 : 3 : clearCache();
890 : 3 : }
891 : :
892 : 3 : void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
893 : : {
894 : 6 : for ( QgsCurve *curve : std::as_const( mCurves ) )
895 : : {
896 : 3 : curve->transformVertices( transform );
897 : : }
898 : 3 : clearCache();
899 : 3 : }
900 : :
901 : 10 : void QgsCompoundCurve::sumUpArea( double &sum ) const
902 : : {
903 : 24 : for ( const QgsCurve *curve : mCurves )
904 : : {
905 : 14 : curve->sumUpArea( sum );
906 : : }
907 : 10 : }
908 : :
909 : 3 : void QgsCompoundCurve::close()
910 : : {
911 : 3 : if ( numPoints() < 1 || isClosed() )
912 : : {
913 : 2 : return;
914 : : }
915 : 1 : addVertex( startPoint() );
916 : 3 : }
917 : :
918 : 4 : bool QgsCompoundCurve::hasCurvedSegments() const
919 : : {
920 : 5 : for ( const QgsCurve *curve : mCurves )
921 : : {
922 : 3 : if ( curve->hasCurvedSegments() )
923 : : {
924 : 2 : return true;
925 : : }
926 : : }
927 : 2 : return false;
928 : 4 : }
929 : :
930 : 48 : double QgsCompoundCurve::vertexAngle( QgsVertexId vertex ) const
931 : : {
932 : 48 : QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
933 : 48 : if ( curveIds.size() == 1 )
934 : : {
935 : 43 : QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
936 : 43 : return curve->vertexAngle( curveIds.at( 0 ).second );
937 : : }
938 : 5 : else if ( curveIds.size() > 1 )
939 : : {
940 : 2 : QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
941 : 2 : QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
942 : 2 : double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
943 : 2 : double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
944 : 2 : return QgsGeometryUtils::averageAngle( angle1, angle2 );
945 : : }
946 : : else
947 : : {
948 : 3 : return 0.0;
949 : : }
950 : 48 : }
951 : :
952 : 34 : double QgsCompoundCurve::segmentLength( QgsVertexId startVertex ) const
953 : : {
954 : 34 : QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
955 : 34 : double length = 0.0;
956 : 63 : for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
957 : : {
958 : 29 : length += mCurves.at( it->first )->segmentLength( it->second );
959 : 29 : }
960 : 34 : return length;
961 : 34 : }
962 : :
963 : 2 : QgsCompoundCurve *QgsCompoundCurve::reversed() const
964 : : {
965 : 2 : QgsCompoundCurve *clone = new QgsCompoundCurve();
966 : 4 : for ( int i = mCurves.count() - 1; i >= 0; --i )
967 : : {
968 : 2 : QgsCurve *reversedCurve = mCurves.at( i )->reversed();
969 : 2 : clone->addCurve( reversedCurve );
970 : 2 : }
971 : 2 : return clone;
972 : 0 : }
973 : :
974 : 11 : QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
975 : : {
976 : 11 : if ( distance < 0 )
977 : 1 : return nullptr;
978 : :
979 : 10 : double distanceTraversed = 0;
980 : 14 : for ( const QgsCurve *curve : mCurves )
981 : : {
982 : 12 : const double thisCurveLength = curve->length();
983 : 12 : if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
984 : : {
985 : : // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
986 : 8 : const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
987 : :
988 : : // point falls on this curve
989 : 8 : return curve->interpolatePoint( distanceToPoint );
990 : : }
991 : :
992 : 4 : distanceTraversed += thisCurveLength;
993 : : }
994 : :
995 : 2 : return nullptr;
996 : 11 : }
997 : :
998 : 13 : QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
999 : : {
1000 : 13 : if ( startDistance < 0 && endDistance < 0 )
1001 : 1 : return createEmptyWithSameType();
1002 : :
1003 : 12 : endDistance = std::max( startDistance, endDistance );
1004 : 12 : std::unique_ptr< QgsCompoundCurve > substring = std::make_unique< QgsCompoundCurve >();
1005 : :
1006 : 12 : double distanceTraversed = 0;
1007 : 22 : for ( const QgsCurve *curve : mCurves )
1008 : : {
1009 : 18 : const double thisCurveLength = curve->length();
1010 : 18 : if ( distanceTraversed + thisCurveLength < startDistance )
1011 : : {
1012 : : // keep going - haven't found start yet, so no need to include this curve at all
1013 : 2 : }
1014 : : else
1015 : : {
1016 : 16 : std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
1017 : 16 : if ( part )
1018 : 16 : substring->addCurve( part.release() );
1019 : 16 : }
1020 : :
1021 : 18 : distanceTraversed += thisCurveLength;
1022 : 18 : if ( distanceTraversed > endDistance )
1023 : 8 : break;
1024 : : }
1025 : :
1026 : 12 : return substring.release();
1027 : 13 : }
1028 : :
1029 : 12 : bool QgsCompoundCurve::addZValue( double zValue )
1030 : : {
1031 : 12 : if ( QgsWkbTypes::hasZ( mWkbType ) )
1032 : 7 : return false;
1033 : :
1034 : 5 : mWkbType = QgsWkbTypes::addZ( mWkbType );
1035 : :
1036 : 12 : for ( QgsCurve *curve : std::as_const( mCurves ) )
1037 : : {
1038 : 7 : curve->addZValue( zValue );
1039 : : }
1040 : 5 : clearCache();
1041 : 5 : return true;
1042 : 12 : }
1043 : :
1044 : 10 : bool QgsCompoundCurve::addMValue( double mValue )
1045 : : {
1046 : 10 : if ( QgsWkbTypes::hasM( mWkbType ) )
1047 : 6 : return false;
1048 : :
1049 : 4 : mWkbType = QgsWkbTypes::addM( mWkbType );
1050 : :
1051 : 11 : for ( QgsCurve *curve : std::as_const( mCurves ) )
1052 : : {
1053 : 7 : curve->addMValue( mValue );
1054 : : }
1055 : 4 : clearCache();
1056 : 4 : return true;
1057 : 10 : }
1058 : :
1059 : 7 : bool QgsCompoundCurve::dropZValue()
1060 : : {
1061 : 7 : if ( !QgsWkbTypes::hasZ( mWkbType ) )
1062 : 4 : return false;
1063 : :
1064 : 3 : mWkbType = QgsWkbTypes::dropZ( mWkbType );
1065 : 8 : for ( QgsCurve *curve : std::as_const( mCurves ) )
1066 : : {
1067 : 5 : curve->dropZValue();
1068 : : }
1069 : 3 : clearCache();
1070 : 3 : return true;
1071 : 7 : }
1072 : :
1073 : 8 : bool QgsCompoundCurve::dropMValue()
1074 : : {
1075 : 8 : if ( !QgsWkbTypes::hasM( mWkbType ) )
1076 : 5 : return false;
1077 : :
1078 : 3 : mWkbType = QgsWkbTypes::dropM( mWkbType );
1079 : 8 : for ( QgsCurve *curve : std::as_const( mCurves ) )
1080 : : {
1081 : 5 : curve->dropMValue();
1082 : : }
1083 : 3 : clearCache();
1084 : 3 : return true;
1085 : 8 : }
1086 : :
1087 : 3 : void QgsCompoundCurve::swapXy()
1088 : : {
1089 : 6 : for ( QgsCurve *curve : std::as_const( mCurves ) )
1090 : : {
1091 : 3 : curve->swapXy();
1092 : : }
1093 : 3 : clearCache();
1094 : 3 : }
|