Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgscircularstring.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 "qgscircularstring.h"
19 : : #include "qgsapplication.h"
20 : : #include "qgscoordinatetransform.h"
21 : : #include "qgsgeometryutils.h"
22 : : #include "qgslinestring.h"
23 : : #include "qgsmaptopixel.h"
24 : : #include "qgspoint.h"
25 : : #include "qgswkbptr.h"
26 : : #include "qgslogger.h"
27 : : #include "qgsgeometrytransformer.h"
28 : : #include "qgsfeedback.h"
29 : :
30 : : #include <QJsonObject>
31 : : #include <QPainter>
32 : : #include <QPainterPath>
33 : : #include <memory>
34 : : #include <nlohmann/json.hpp>
35 : :
36 : 1316 : QgsCircularString::QgsCircularString()
37 : 2632 : {
38 : 1316 : mWkbType = QgsWkbTypes::CircularString;
39 : 1316 : }
40 : :
41 : 9 : QgsCircularString::QgsCircularString( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3 )
42 : 18 : {
43 : : //get wkb type from first point
44 : 9 : bool hasZ = p1.is3D();
45 : 9 : bool hasM = p1.isMeasure();
46 : 9 : mWkbType = QgsWkbTypes::CircularString;
47 : :
48 : 9 : mX.resize( 3 );
49 : 9 : mX[ 0 ] = p1.x();
50 : 9 : mX[ 1 ] = p2.x();
51 : 9 : mX[ 2 ] = p3.x();
52 : 9 : mY.resize( 3 );
53 : 9 : mY[ 0 ] = p1.y();
54 : 9 : mY[ 1 ] = p2.y();
55 : 9 : mY[ 2 ] = p3.y();
56 : 9 : if ( hasZ )
57 : : {
58 : 4 : mWkbType = QgsWkbTypes::addZ( mWkbType );
59 : 4 : mZ.resize( 3 );
60 : 4 : mZ[ 0 ] = p1.z();
61 : 4 : mZ[ 1 ] = p2.z();
62 : 4 : mZ[ 2 ] = p3.z();
63 : 4 : }
64 : 9 : if ( hasM )
65 : : {
66 : 4 : mWkbType = QgsWkbTypes::addM( mWkbType );
67 : 4 : mM.resize( 3 );
68 : 4 : mM[ 0 ] = p1.m();
69 : 4 : mM[ 1 ] = p2.m();
70 : 4 : mM[ 2 ] = p3.m();
71 : 4 : }
72 : 9 : }
73 : :
74 : 5 : QgsCircularString QgsCircularString::fromTwoPointsAndCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint ¢er, const bool useShortestArc )
75 : : {
76 : 5 : const QgsPoint midPoint = QgsGeometryUtils::segmentMidPointFromCenter( p1, p2, center, useShortestArc );
77 : 5 : return QgsCircularString( p1, midPoint, p2 );
78 : 5 : }
79 : :
80 : 117 : bool QgsCircularString::equals( const QgsCurve &other ) const
81 : : {
82 : 117 : const QgsCircularString *otherLine = dynamic_cast< const QgsCircularString * >( &other );
83 : 117 : if ( !otherLine )
84 : 1 : return false;
85 : :
86 : 116 : if ( mWkbType != otherLine->mWkbType )
87 : 2 : return false;
88 : :
89 : 114 : if ( mX.count() != otherLine->mX.count() )
90 : 2 : return false;
91 : :
92 : 484 : for ( int i = 0; i < mX.count(); ++i )
93 : : {
94 : 382 : if ( !qgsDoubleNear( mX.at( i ), otherLine->mX.at( i ) )
95 : 382 : || !qgsDoubleNear( mY.at( i ), otherLine->mY.at( i ) ) )
96 : 6 : return false;
97 : :
98 : 376 : if ( is3D() && !qgsDoubleNear( mZ.at( i ), otherLine->mZ.at( i ) ) )
99 : 2 : return false;
100 : :
101 : 374 : if ( isMeasure() && !qgsDoubleNear( mM.at( i ), otherLine->mM.at( i ) ) )
102 : 2 : return false;
103 : 372 : }
104 : :
105 : 102 : return true;
106 : 117 : }
107 : :
108 : 8 : QgsCircularString *QgsCircularString::createEmptyWithSameType() const
109 : : {
110 : 8 : auto result = std::make_unique< QgsCircularString >();
111 : 8 : result->mWkbType = mWkbType;
112 : 8 : return result.release();
113 : 8 : }
114 : :
115 : 503 : QString QgsCircularString::geometryType() const
116 : : {
117 : 1006 : return QStringLiteral( "CircularString" );
118 : : }
119 : :
120 : 2 : int QgsCircularString::dimension() const
121 : : {
122 : 2 : return 1;
123 : : }
124 : :
125 : 309 : QgsCircularString *QgsCircularString::clone() const
126 : : {
127 : 309 : return new QgsCircularString( *this );
128 : 0 : }
129 : :
130 : 473 : void QgsCircularString::clear()
131 : : {
132 : 473 : mWkbType = QgsWkbTypes::CircularString;
133 : 473 : mX.clear();
134 : 473 : mY.clear();
135 : 473 : mZ.clear();
136 : 473 : mM.clear();
137 : 473 : clearCache();
138 : 473 : }
139 : :
140 : 27 : QgsRectangle QgsCircularString::calculateBoundingBox() const
141 : : {
142 : 27 : QgsRectangle bbox;
143 : 27 : int nPoints = numPoints();
144 : 53 : for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
145 : : {
146 : 26 : if ( i == 0 )
147 : : {
148 : 17 : bbox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
149 : 17 : }
150 : : else
151 : : {
152 : 9 : QgsRectangle segmentBox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
153 : 9 : bbox.combineExtentWith( segmentBox );
154 : : }
155 : 26 : }
156 : :
157 : 27 : if ( nPoints > 0 && nPoints % 2 == 0 )
158 : : {
159 : 8 : if ( nPoints == 2 )
160 : : {
161 : 8 : bbox.combineExtentWith( mX[ 0 ], mY[ 0 ] );
162 : 8 : }
163 : 8 : bbox.combineExtentWith( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
164 : 8 : }
165 : 27 : return bbox;
166 : 0 : }
167 : :
168 : 26 : QgsRectangle QgsCircularString::segmentBoundingBox( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3 )
169 : : {
170 : : double centerX, centerY, radius;
171 : 26 : QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
172 : :
173 : 26 : double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
174 : 26 : double p2Angle = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
175 : 26 : double p3Angle = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
176 : :
177 : : //start point, end point and compass points in between can be on bounding box
178 : 26 : QgsRectangle bbox( pt1.x(), pt1.y(), pt1.x(), pt1.y() );
179 : 26 : bbox.combineExtentWith( pt3.x(), pt3.y() );
180 : :
181 : 26 : QgsPointSequence compassPoints = compassPointsOnSegment( p1Angle, p2Angle, p3Angle, centerX, centerY, radius );
182 : 26 : QgsPointSequence::const_iterator cpIt = compassPoints.constBegin();
183 : 93 : for ( ; cpIt != compassPoints.constEnd(); ++cpIt )
184 : : {
185 : 67 : bbox.combineExtentWith( cpIt->x(), cpIt->y() );
186 : 67 : }
187 : : return bbox;
188 : 26 : }
189 : :
190 : 26 : QgsPointSequence QgsCircularString::compassPointsOnSegment( double p1Angle, double p2Angle, double p3Angle, double centerX, double centerY, double radius )
191 : : {
192 : 26 : QgsPointSequence pointList;
193 : :
194 : 26 : QgsPoint nPoint( centerX, centerY + radius );
195 : 26 : QgsPoint ePoint( centerX + radius, centerY );
196 : 26 : QgsPoint sPoint( centerX, centerY - radius );
197 : 26 : QgsPoint wPoint( centerX - radius, centerY );
198 : :
199 : 26 : if ( p3Angle >= p1Angle )
200 : : {
201 : 14 : if ( p2Angle > p1Angle && p2Angle < p3Angle )
202 : : {
203 : 2 : if ( p1Angle <= 90 && p3Angle >= 90 )
204 : : {
205 : 0 : pointList.append( nPoint );
206 : 0 : }
207 : 2 : if ( p1Angle <= 180 && p3Angle >= 180 )
208 : : {
209 : 2 : pointList.append( wPoint );
210 : 2 : }
211 : 2 : if ( p1Angle <= 270 && p3Angle >= 270 )
212 : : {
213 : 0 : pointList.append( sPoint );
214 : 0 : }
215 : 2 : }
216 : : else
217 : : {
218 : 12 : pointList.append( ePoint );
219 : 12 : if ( p1Angle >= 90 || p3Angle <= 90 )
220 : : {
221 : 10 : pointList.append( nPoint );
222 : 10 : }
223 : 12 : if ( p1Angle >= 180 || p3Angle <= 180 )
224 : : {
225 : 6 : pointList.append( wPoint );
226 : 6 : }
227 : 12 : if ( p1Angle >= 270 || p3Angle <= 270 )
228 : : {
229 : 8 : pointList.append( sPoint );
230 : 8 : }
231 : : }
232 : 14 : }
233 : : else
234 : : {
235 : 12 : if ( p2Angle < p1Angle && p2Angle > p3Angle )
236 : : {
237 : 8 : if ( p1Angle >= 270 && p3Angle <= 270 )
238 : : {
239 : 5 : pointList.append( sPoint );
240 : 5 : }
241 : 8 : if ( p1Angle >= 180 && p3Angle <= 180 )
242 : : {
243 : 8 : pointList.append( wPoint );
244 : 8 : }
245 : 8 : if ( p1Angle >= 90 && p3Angle <= 90 )
246 : : {
247 : 6 : pointList.append( nPoint );
248 : 6 : }
249 : 8 : }
250 : : else
251 : : {
252 : 4 : pointList.append( ePoint );
253 : 4 : if ( p1Angle <= 270 || p3Angle >= 270 )
254 : : {
255 : 2 : pointList.append( sPoint );
256 : 2 : }
257 : 4 : if ( p1Angle <= 180 || p3Angle >= 180 )
258 : : {
259 : 2 : pointList.append( wPoint );
260 : 2 : }
261 : 4 : if ( p1Angle <= 90 || p3Angle >= 90 )
262 : : {
263 : 2 : pointList.append( nPoint );
264 : 2 : }
265 : : }
266 : : }
267 : 26 : return pointList;
268 : 26 : }
269 : :
270 : 33 : bool QgsCircularString::fromWkb( QgsConstWkbPtr &wkbPtr )
271 : : {
272 : 33 : if ( !wkbPtr )
273 : 1 : return false;
274 : :
275 : 32 : QgsWkbTypes::Type type = wkbPtr.readHeader();
276 : 32 : if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CircularString )
277 : : {
278 : 1 : return false;
279 : : }
280 : 31 : clearCache();
281 : 31 : mWkbType = type;
282 : :
283 : : //type
284 : 31 : bool hasZ = is3D();
285 : 31 : bool hasM = isMeasure();
286 : 31 : int nVertices = 0;
287 : 31 : wkbPtr >> nVertices;
288 : 31 : mX.resize( nVertices );
289 : 31 : mY.resize( nVertices );
290 : 31 : hasZ ? mZ.resize( nVertices ) : mZ.clear();
291 : 31 : hasM ? mM.resize( nVertices ) : mM.clear();
292 : 148 : for ( int i = 0; i < nVertices; ++i )
293 : : {
294 : 117 : wkbPtr >> mX[i];
295 : 117 : wkbPtr >> mY[i];
296 : 117 : if ( hasZ )
297 : : {
298 : 67 : wkbPtr >> mZ[i];
299 : 67 : }
300 : 117 : if ( hasM )
301 : : {
302 : 52 : wkbPtr >> mM[i];
303 : 52 : }
304 : 117 : }
305 : :
306 : 31 : return true;
307 : 33 : }
308 : :
309 : 451 : bool QgsCircularString::fromWkt( const QString &wkt )
310 : : {
311 : 451 : clear();
312 : :
313 : 451 : QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
314 : :
315 : 451 : if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::CircularString )
316 : 1 : return false;
317 : 450 : mWkbType = parts.first;
318 : :
319 : 450 : parts.second = parts.second.remove( '(' ).remove( ')' );
320 : 450 : QString secondWithoutParentheses = parts.second;
321 : 450 : secondWithoutParentheses = secondWithoutParentheses.simplified().remove( ' ' );
322 : 500 : if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
323 : 50 : secondWithoutParentheses.isEmpty() )
324 : 400 : return true;
325 : :
326 : 50 : QgsPointSequence points = QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() );
327 : 50 : if ( points.isEmpty() )
328 : 2 : return false;
329 : :
330 : 48 : setPoints( points );
331 : 48 : return true;
332 : 451 : }
333 : :
334 : 76 : int QgsCircularString::wkbSize( QgsAbstractGeometry::WkbFlags ) const
335 : : {
336 : 76 : int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
337 : 76 : binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
338 : 76 : return binarySize;
339 : : }
340 : :
341 : 31 : QByteArray QgsCircularString::asWkb( WkbFlags flags ) const
342 : : {
343 : 31 : QByteArray wkbArray;
344 : 31 : wkbArray.resize( QgsCircularString::wkbSize( flags ) );
345 : 31 : QgsWkbPtr wkb( wkbArray );
346 : 31 : wkb << static_cast<char>( QgsApplication::endian() );
347 : 31 : wkb << static_cast<quint32>( wkbType() );
348 : 31 : QgsPointSequence pts;
349 : 31 : points( pts );
350 : 31 : QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
351 : 31 : return wkbArray;
352 : 31 : }
353 : :
354 : 498 : QString QgsCircularString::asWkt( int precision ) const
355 : : {
356 : 498 : QString wkt = wktTypeStr() + ' ';
357 : :
358 : 498 : if ( isEmpty() )
359 : 400 : wkt += QLatin1String( "EMPTY" );
360 : : else
361 : : {
362 : 98 : QgsPointSequence pts;
363 : 98 : points( pts );
364 : 98 : wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
365 : 98 : }
366 : 498 : return wkt;
367 : 498 : }
368 : :
369 : 4 : QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
370 : : {
371 : : // GML2 does not support curves
372 : 4 : std::unique_ptr< QgsLineString > line( curveToLine() );
373 : 4 : QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
374 : 4 : return gml;
375 : 4 : }
376 : :
377 : 15 : QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
378 : : {
379 : 15 : QgsPointSequence pts;
380 : 15 : points( pts );
381 : :
382 : 30 : QDomElement elemCurve = doc.createElementNS( ns, QStringLiteral( "Curve" ) );
383 : :
384 : 15 : if ( isEmpty() )
385 : 1 : return elemCurve;
386 : :
387 : 28 : QDomElement elemSegments = doc.createElementNS( ns, QStringLiteral( "segments" ) );
388 : 28 : QDomElement elemArcString = doc.createElementNS( ns, QStringLiteral( "ArcString" ) );
389 : 14 : elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
390 : 14 : elemSegments.appendChild( elemArcString );
391 : 14 : elemCurve.appendChild( elemSegments );
392 : 14 : return elemCurve;
393 : 15 : }
394 : :
395 : :
396 : 3 : json QgsCircularString::asJsonObject( int precision ) const
397 : : {
398 : : // GeoJSON does not support curves
399 : 3 : std::unique_ptr< QgsLineString > line( curveToLine() );
400 : 3 : return line->asJsonObject( precision );
401 : 3 : }
402 : :
403 : 612 : bool QgsCircularString::isEmpty() const
404 : : {
405 : 612 : return mX.isEmpty();
406 : : }
407 : :
408 : 5 : bool QgsCircularString::isValid( QString &error, int flags ) const
409 : : {
410 : 5 : if ( !isEmpty() && ( numPoints() < 3 ) )
411 : : {
412 : 1 : error = QObject::tr( "CircularString has less than 3 points and is not empty." );
413 : 1 : return false;
414 : : }
415 : 4 : return QgsCurve::isValid( error, flags );
416 : 5 : }
417 : :
418 : : //curve interface
419 : 28 : double QgsCircularString::length() const
420 : : {
421 : 28 : int nPoints = numPoints();
422 : 28 : double length = 0;
423 : 59 : for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
424 : : {
425 : 31 : length += QgsGeometryUtils::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
426 : 31 : }
427 : 28 : return length;
428 : : }
429 : :
430 : 50 : QgsPoint QgsCircularString::startPoint() const
431 : : {
432 : 50 : if ( numPoints() < 1 )
433 : : {
434 : 1 : return QgsPoint();
435 : : }
436 : 49 : return pointN( 0 );
437 : 50 : }
438 : :
439 : 48 : QgsPoint QgsCircularString::endPoint() const
440 : : {
441 : 48 : if ( numPoints() < 1 )
442 : : {
443 : 1 : return QgsPoint();
444 : : }
445 : 47 : return pointN( numPoints() - 1 );
446 : 48 : }
447 : :
448 : 69 : QgsLineString *QgsCircularString::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
449 : : {
450 : 69 : QgsLineString *line = new QgsLineString();
451 : 69 : QgsPointSequence points;
452 : 69 : int nPoints = numPoints();
453 : :
454 : 162 : for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
455 : : {
456 : 93 : QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
457 : 93 : }
458 : :
459 : 69 : line->setPoints( points );
460 : 69 : return line;
461 : 69 : }
462 : :
463 : 6 : QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
464 : : {
465 : : // prepare result
466 : 6 : std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
467 : :
468 : 12 : bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM,
469 : 6 : result->mX, result->mY, result->mZ, result->mM );
470 : 6 : if ( res )
471 : 6 : return result.release();
472 : : else
473 : 0 : return nullptr;
474 : 6 : }
475 : :
476 : 21 : bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
477 : : {
478 : 21 : if ( mX.count() <= 3 )
479 : 9 : return false; // don't create degenerate lines
480 : 12 : bool result = false;
481 : 12 : double prevX = mX.at( 0 );
482 : 12 : double prevY = mY.at( 0 );
483 : 12 : bool hasZ = is3D();
484 : 12 : bool useZ = hasZ && useZValues;
485 : 12 : double prevZ = useZ ? mZ.at( 0 ) : 0;
486 : 12 : int i = 1;
487 : 12 : int remaining = mX.count();
488 : : // we have to consider points in pairs, since a segment can validly have the same start and
489 : : // end if it has a different curve point
490 : 32 : while ( i + 1 < remaining )
491 : : {
492 : 20 : double currentCurveX = mX.at( i );
493 : 20 : double currentCurveY = mY.at( i );
494 : 20 : double currentX = mX.at( i + 1 );
495 : 20 : double currentY = mY.at( i + 1 );
496 : 20 : double currentZ = useZ ? mZ.at( i + 1 ) : 0;
497 : 21 : if ( qgsDoubleNear( currentCurveX, prevX, epsilon ) &&
498 : 12 : qgsDoubleNear( currentCurveY, prevY, epsilon ) &&
499 : 4 : qgsDoubleNear( currentX, prevX, epsilon ) &&
500 : 4 : qgsDoubleNear( currentY, prevY, epsilon ) &&
501 : 4 : ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
502 : : {
503 : 3 : result = true;
504 : : // remove point
505 : 3 : mX.removeAt( i );
506 : 3 : mX.removeAt( i );
507 : 3 : mY.removeAt( i );
508 : 3 : mY.removeAt( i );
509 : 3 : if ( hasZ )
510 : : {
511 : 1 : mZ.removeAt( i );
512 : 1 : mZ.removeAt( i );
513 : 1 : }
514 : 3 : remaining -= 2;
515 : 3 : }
516 : : else
517 : : {
518 : 17 : prevX = currentX;
519 : 17 : prevY = currentY;
520 : 17 : prevZ = currentZ;
521 : 17 : i += 2;
522 : : }
523 : : }
524 : 12 : return result;
525 : 21 : }
526 : :
527 : 1801 : int QgsCircularString::numPoints() const
528 : : {
529 : 1801 : return std::min( mX.size(), mY.size() );
530 : : }
531 : :
532 : 1635 : QgsPoint QgsCircularString::pointN( int i ) const
533 : : {
534 : 1635 : if ( i < 0 || std::min( mX.size(), mY.size() ) <= i )
535 : : {
536 : 2 : return QgsPoint();
537 : : }
538 : :
539 : 1633 : double x = mX.at( i );
540 : 1633 : double y = mY.at( i );
541 : 1633 : double z = 0;
542 : 1633 : double m = 0;
543 : :
544 : 1633 : if ( is3D() )
545 : : {
546 : 669 : z = mZ.at( i );
547 : 669 : }
548 : 1633 : if ( isMeasure() )
549 : : {
550 : 635 : m = mM.at( i );
551 : 635 : }
552 : :
553 : 1633 : QgsWkbTypes::Type t = QgsWkbTypes::Point;
554 : 1633 : if ( is3D() && isMeasure() )
555 : : {
556 : 522 : t = QgsWkbTypes::PointZM;
557 : 522 : }
558 : 1111 : else if ( is3D() )
559 : : {
560 : 147 : t = QgsWkbTypes::PointZ;
561 : 147 : }
562 : 964 : else if ( isMeasure() )
563 : : {
564 : 113 : t = QgsWkbTypes::PointM;
565 : 113 : }
566 : 1633 : return QgsPoint( t, x, y, z, m );
567 : 1635 : }
568 : :
569 : 64 : double QgsCircularString::xAt( int index ) const
570 : : {
571 : 64 : if ( index >= 0 && index < mX.size() )
572 : 60 : return mX.at( index );
573 : : else
574 : 4 : return 0.0;
575 : 64 : }
576 : :
577 : 64 : double QgsCircularString::yAt( int index ) const
578 : : {
579 : 64 : if ( index >= 0 && index < mY.size() )
580 : 60 : return mY.at( index );
581 : : else
582 : 4 : return 0.0;
583 : 64 : }
584 : :
585 : 6 : bool QgsCircularString::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
586 : : {
587 : 6 : if ( !transformer )
588 : 0 : return false;
589 : :
590 : 6 : bool hasZ = is3D();
591 : 6 : bool hasM = isMeasure();
592 : 6 : int size = mX.size();
593 : :
594 : 6 : double *srcX = mX.data();
595 : 6 : double *srcY = mY.data();
596 : 6 : double *srcM = hasM ? mM.data() : nullptr;
597 : 6 : double *srcZ = hasZ ? mZ.data() : nullptr;
598 : :
599 : 6 : bool res = true;
600 : 19 : for ( int i = 0; i < size; ++i )
601 : : {
602 : 15 : double x = *srcX;
603 : 15 : double y = *srcY;
604 : 15 : double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
605 : 15 : double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
606 : 15 : if ( !transformer->transformPoint( x, y, z, m ) )
607 : : {
608 : 2 : res = false;
609 : 2 : break;
610 : : }
611 : :
612 : 13 : *srcX++ = x;
613 : 13 : *srcY++ = y;
614 : 13 : if ( hasM )
615 : 13 : *srcM++ = m;
616 : 13 : if ( hasZ )
617 : 13 : *srcZ++ = z;
618 : :
619 : 13 : if ( feedback && feedback->isCanceled() )
620 : : {
621 : 0 : res = false;
622 : 0 : break;
623 : : }
624 : 13 : }
625 : 6 : clearCache();
626 : 6 : return res;
627 : 6 : }
628 : :
629 : 4 : void QgsCircularString::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
630 : : {
631 : 4 : bool hasZ = is3D();
632 : 4 : bool hasM = isMeasure();
633 : 4 : int size = mX.size();
634 : :
635 : 4 : double *srcX = mX.data(); // clazy:exclude=detaching-member
636 : 4 : double *srcY = mY.data(); // clazy:exclude=detaching-member
637 : 4 : double *srcM = hasM ? mM.data() : nullptr; // clazy:exclude=detaching-member
638 : 4 : double *srcZ = hasZ ? mZ.data() : nullptr; // clazy:exclude=detaching-member
639 : :
640 : 4 : double *destX = srcX;
641 : 4 : double *destY = srcY;
642 : 4 : double *destM = srcM;
643 : 4 : double *destZ = srcZ;
644 : :
645 : 4 : int filteredPoints = 0;
646 : 15 : for ( int i = 0; i < size; ++i )
647 : : {
648 : 11 : double x = *srcX++;
649 : 11 : double y = *srcY++;
650 : 11 : double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
651 : 11 : double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
652 : :
653 : 11 : if ( filter( QgsPoint( x, y, z, m ) ) )
654 : : {
655 : 8 : filteredPoints++;
656 : 8 : *destX++ = x;
657 : 8 : *destY++ = y;
658 : 8 : if ( hasM )
659 : 8 : *destM++ = m;
660 : 8 : if ( hasZ )
661 : 8 : *destZ++ = z;
662 : 8 : }
663 : 11 : }
664 : :
665 : 4 : mX.resize( filteredPoints );
666 : 4 : mY.resize( filteredPoints );
667 : 4 : if ( hasZ )
668 : 3 : mZ.resize( filteredPoints );
669 : 4 : if ( hasM )
670 : 3 : mM.resize( filteredPoints );
671 : :
672 : 4 : clearCache();
673 : 4 : }
674 : :
675 : 4 : void QgsCircularString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
676 : : {
677 : 4 : bool hasZ = is3D();
678 : 4 : bool hasM = isMeasure();
679 : 4 : int size = mX.size();
680 : :
681 : 4 : double *srcX = mX.data();
682 : 4 : double *srcY = mY.data();
683 : 4 : double *srcM = hasM ? mM.data() : nullptr;
684 : 4 : double *srcZ = hasZ ? mZ.data() : nullptr;
685 : :
686 : 17 : for ( int i = 0; i < size; ++i )
687 : : {
688 : 13 : double x = *srcX;
689 : 13 : double y = *srcY;
690 : 13 : double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
691 : 13 : double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
692 : 13 : QgsPoint res = transform( QgsPoint( x, y, z, m ) );
693 : 13 : *srcX++ = res.x();
694 : 13 : *srcY++ = res.y();
695 : 13 : if ( hasM )
696 : 13 : *srcM++ = res.m();
697 : 13 : if ( hasZ )
698 : 13 : *srcZ++ = res.z();
699 : 13 : }
700 : 4 : clearCache();
701 : 4 : }
702 : :
703 : 244 : void QgsCircularString::points( QgsPointSequence &pts ) const
704 : : {
705 : 244 : pts.clear();
706 : 244 : int nPts = numPoints();
707 : 993 : for ( int i = 0; i < nPts; ++i )
708 : : {
709 : 749 : pts.push_back( pointN( i ) );
710 : 749 : }
711 : 244 : }
712 : :
713 : 466 : void QgsCircularString::setPoints( const QgsPointSequence &points )
714 : : {
715 : 466 : clearCache();
716 : :
717 : 466 : if ( points.empty() )
718 : : {
719 : 2 : mWkbType = QgsWkbTypes::CircularString;
720 : 2 : mX.clear();
721 : 2 : mY.clear();
722 : 2 : mZ.clear();
723 : 2 : mM.clear();
724 : 2 : return;
725 : : }
726 : :
727 : : //get wkb type from first point
728 : 464 : const QgsPoint &firstPt = points.at( 0 );
729 : 464 : bool hasZ = firstPt.is3D();
730 : 464 : bool hasM = firstPt.isMeasure();
731 : :
732 : 464 : setZMTypeFromSubGeometry( &firstPt, QgsWkbTypes::CircularString );
733 : :
734 : 464 : mX.resize( points.size() );
735 : 464 : mY.resize( points.size() );
736 : 464 : if ( hasZ )
737 : : {
738 : 188 : mZ.resize( points.size() );
739 : 188 : }
740 : : else
741 : : {
742 : 276 : mZ.clear();
743 : : }
744 : 464 : if ( hasM )
745 : : {
746 : 168 : mM.resize( points.size() );
747 : 168 : }
748 : : else
749 : : {
750 : 296 : mM.clear();
751 : : }
752 : :
753 : 1980 : for ( int i = 0; i < points.size(); ++i )
754 : : {
755 : 1516 : mX[i] = points[i].x();
756 : 1516 : mY[i] = points[i].y();
757 : 1516 : if ( hasZ )
758 : : {
759 : 632 : double z = points.at( i ).z();
760 : 632 : mZ[i] = std::isnan( z ) ? 0 : z;
761 : 632 : }
762 : 1516 : if ( hasM )
763 : : {
764 : 552 : double m = points.at( i ).m();
765 : 552 : mM[i] = std::isnan( m ) ? 0 : m;
766 : 552 : }
767 : 1516 : }
768 : 466 : }
769 : :
770 : 0 : void QgsCircularString::draw( QPainter &p ) const
771 : : {
772 : 0 : QPainterPath path;
773 : 0 : addToPainterPath( path );
774 : 0 : p.drawPath( path );
775 : 0 : }
776 : :
777 : 6 : void QgsCircularString::transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d, bool transformZ )
778 : : {
779 : 6 : clearCache();
780 : :
781 : 6 : double *zArray = mZ.data();
782 : :
783 : 6 : bool hasZ = is3D();
784 : 6 : int nPoints = numPoints();
785 : 6 : bool useDummyZ = !hasZ || !transformZ;
786 : 6 : if ( useDummyZ )
787 : : {
788 : 6 : zArray = new double[nPoints];
789 : 18 : for ( int i = 0; i < nPoints; ++i )
790 : : {
791 : 12 : zArray[i] = 0;
792 : 12 : }
793 : 6 : }
794 : 6 : ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
795 : 6 : if ( useDummyZ )
796 : : {
797 : 6 : delete[] zArray;
798 : 6 : }
799 : 6 : }
800 : :
801 : 3 : void QgsCircularString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
802 : : {
803 : 3 : clearCache();
804 : :
805 : 3 : int nPoints = numPoints();
806 : 3 : bool hasZ = is3D();
807 : 3 : bool hasM = isMeasure();
808 : 9 : for ( int i = 0; i < nPoints; ++i )
809 : : {
810 : : qreal x, y;
811 : 6 : t.map( mX.at( i ), mY.at( i ), &x, &y );
812 : 6 : mX[i] = x;
813 : 6 : mY[i] = y;
814 : 6 : if ( hasZ )
815 : : {
816 : 6 : mZ[i] = mZ.at( i ) * zScale + zTranslate;
817 : 6 : }
818 : 6 : if ( hasM )
819 : : {
820 : 6 : mM[i] = mM.at( i ) * mScale + mTranslate;
821 : 6 : }
822 : 6 : }
823 : 3 : }
824 : :
825 : 6 : void QgsCircularString::addToPainterPath( QPainterPath &path ) const
826 : : {
827 : 6 : int nPoints = numPoints();
828 : 6 : if ( nPoints < 1 )
829 : : {
830 : 1 : return;
831 : : }
832 : :
833 : 5 : if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
834 : : {
835 : 5 : path.moveTo( QPointF( mX[0], mY[0] ) );
836 : 5 : }
837 : :
838 : 8 : for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
839 : : {
840 : 3 : QgsPointSequence pt;
841 : 3 : QgsGeometryUtils::segmentizeArc( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ), pt );
842 : 543 : for ( int j = 1; j < pt.size(); ++j )
843 : : {
844 : 540 : path.lineTo( pt.at( j ).x(), pt.at( j ).y() );
845 : 540 : }
846 : : #if 0
847 : : //arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
848 : : #endif
849 : 3 : }
850 : :
851 : : //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
852 : 5 : if ( nPoints % 2 == 0 )
853 : : {
854 : 2 : path.lineTo( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
855 : 2 : }
856 : 6 : }
857 : :
858 : : #if 0
859 : : void QgsCircularString::arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
860 : : {
861 : : double centerX, centerY, radius;
862 : : QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ),
863 : : radius, centerX, centerY );
864 : :
865 : : double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
866 : : double sweepAngle = QgsGeometryUtils::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
867 : :
868 : : double diameter = 2 * radius;
869 : : path.arcTo( centerX - radius, centerY - radius, diameter, diameter, p1Angle, sweepAngle );
870 : : }
871 : : #endif
872 : :
873 : 0 : void QgsCircularString::drawAsPolygon( QPainter &p ) const
874 : : {
875 : 0 : draw( p );
876 : 0 : }
877 : :
878 : 20 : bool QgsCircularString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
879 : : {
880 : 20 : if ( position.vertex >= mX.size() || position.vertex < 1 )
881 : : {
882 : 6 : return false;
883 : : }
884 : :
885 : 14 : mX.insert( position.vertex, vertex.x() );
886 : 14 : mY.insert( position.vertex, vertex.y() );
887 : 14 : if ( is3D() )
888 : : {
889 : 4 : mZ.insert( position.vertex, vertex.z() );
890 : 4 : }
891 : 14 : if ( isMeasure() )
892 : : {
893 : 4 : mM.insert( position.vertex, vertex.m() );
894 : 4 : }
895 : :
896 : 14 : bool vertexNrEven = ( position.vertex % 2 == 0 );
897 : 14 : if ( vertexNrEven )
898 : : {
899 : 2 : insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
900 : 2 : }
901 : : else
902 : : {
903 : 12 : insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
904 : : }
905 : 14 : clearCache(); //set bounding box invalid
906 : 14 : return true;
907 : 20 : }
908 : :
909 : 38 : bool QgsCircularString::moveVertex( QgsVertexId position, const QgsPoint &newPos )
910 : : {
911 : 38 : if ( position.vertex < 0 || position.vertex >= mX.size() )
912 : : {
913 : 5 : return false;
914 : : }
915 : :
916 : 33 : mX[position.vertex] = newPos.x();
917 : 33 : mY[position.vertex] = newPos.y();
918 : 33 : if ( is3D() && newPos.is3D() )
919 : : {
920 : 8 : mZ[position.vertex] = newPos.z();
921 : 8 : }
922 : 33 : if ( isMeasure() && newPos.isMeasure() )
923 : : {
924 : 6 : mM[position.vertex] = newPos.m();
925 : 6 : }
926 : 33 : clearCache(); //set bounding box invalid
927 : 33 : return true;
928 : 38 : }
929 : :
930 : 12 : bool QgsCircularString::deleteVertex( QgsVertexId position )
931 : : {
932 : 12 : int nVertices = this->numPoints();
933 : 12 : if ( nVertices < 4 ) //circular string must have at least 3 vertices
934 : : {
935 : 6 : clear();
936 : 6 : return true;
937 : : }
938 : 6 : if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
939 : : {
940 : 2 : return false;
941 : : }
942 : :
943 : 4 : if ( position.vertex < ( nVertices - 2 ) )
944 : : {
945 : : //remove this and the following vertex
946 : 4 : deleteVertex( position.vertex + 1 );
947 : 4 : deleteVertex( position.vertex );
948 : 4 : }
949 : : else //remove this and the preceding vertex
950 : : {
951 : 0 : deleteVertex( position.vertex );
952 : 0 : deleteVertex( position.vertex - 1 );
953 : : }
954 : :
955 : 4 : clearCache(); //set bounding box invalid
956 : 4 : return true;
957 : 12 : }
958 : :
959 : 8 : void QgsCircularString::deleteVertex( int i )
960 : : {
961 : 8 : mX.remove( i );
962 : 8 : mY.remove( i );
963 : 8 : if ( is3D() )
964 : : {
965 : 4 : mZ.remove( i );
966 : 4 : }
967 : 8 : if ( isMeasure() )
968 : : {
969 : 4 : mM.remove( i );
970 : 4 : }
971 : 8 : clearCache();
972 : 8 : }
973 : :
974 : 46 : double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
975 : : {
976 : 46 : double minDist = std::numeric_limits<double>::max();
977 : 46 : QgsPoint minDistSegmentPoint;
978 : 46 : QgsVertexId minDistVertexAfter;
979 : 46 : int minDistLeftOf = 0;
980 : :
981 : 46 : double currentDist = 0.0;
982 : :
983 : 46 : int nPoints = numPoints();
984 : 89 : for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
985 : : {
986 : 43 : currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
987 : 43 : if ( currentDist < minDist )
988 : : {
989 : 43 : minDist = currentDist;
990 : 43 : minDistSegmentPoint = segmentPt;
991 : 43 : minDistVertexAfter.vertex = vertexAfter.vertex + i;
992 : 43 : if ( leftOf )
993 : : {
994 : 43 : minDistLeftOf = *leftOf;
995 : 43 : }
996 : 43 : }
997 : 43 : }
998 : :
999 : 46 : if ( minDist == std::numeric_limits<double>::max() )
1000 : 3 : return -1; // error: no segments
1001 : :
1002 : 43 : segmentPt = minDistSegmentPoint;
1003 : 43 : vertexAfter = minDistVertexAfter;
1004 : 43 : vertexAfter.part = 0;
1005 : 43 : vertexAfter.ring = 0;
1006 : 43 : if ( leftOf )
1007 : : {
1008 : 43 : *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
1009 : 43 : }
1010 : 43 : return minDist;
1011 : 46 : }
1012 : :
1013 : 228 : bool QgsCircularString::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
1014 : : {
1015 : 228 : if ( node < 0 || node >= numPoints() )
1016 : : {
1017 : 16 : return false;
1018 : : }
1019 : 212 : point = pointN( node );
1020 : 212 : type = ( node % 2 == 0 ) ? QgsVertexId::SegmentVertex : QgsVertexId::CurveVertex;
1021 : 212 : return true;
1022 : 228 : }
1023 : :
1024 : 20 : void QgsCircularString::sumUpArea( double &sum ) const
1025 : : {
1026 : 20 : int maxIndex = numPoints() - 2;
1027 : :
1028 : 40 : for ( int i = 0; i < maxIndex; i += 2 )
1029 : : {
1030 : 20 : QgsPoint p1( mX[i], mY[i] );
1031 : 20 : QgsPoint p2( mX[i + 1], mY[i + 1] );
1032 : 20 : QgsPoint p3( mX[i + 2], mY[i + 2] );
1033 : :
1034 : : //segment is a full circle, p2 is the center point
1035 : 20 : if ( p1 == p3 )
1036 : : {
1037 : 2 : double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
1038 : 2 : sum += M_PI * r2;
1039 : 2 : continue;
1040 : : }
1041 : :
1042 : 18 : sum += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
1043 : :
1044 : : //calculate area between circle and chord, then sum / subtract from total area
1045 : 18 : double midPointX = ( p1.x() + p3.x() ) / 2.0;
1046 : 18 : double midPointY = ( p1.y() + p3.y() ) / 2.0;
1047 : :
1048 : : double radius, centerX, centerY;
1049 : 18 : QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
1050 : :
1051 : 18 : double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
1052 : 18 : double r2 = radius * radius;
1053 : :
1054 : 18 : if ( d > radius )
1055 : : {
1056 : : //d cannot be greater than radius, something must be wrong...
1057 : 0 : continue;
1058 : : }
1059 : :
1060 : 18 : bool circlePointLeftOfLine = QgsGeometryUtils::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1061 : 18 : bool centerPointLeftOfLine = QgsGeometryUtils::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1062 : :
1063 : 18 : double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
1064 : 18 : double circleChordArea = 0;
1065 : 18 : if ( circlePointLeftOfLine == centerPointLeftOfLine )
1066 : : {
1067 : 7 : circleChordArea = M_PI * r2 * ( 1 - cov );
1068 : 7 : }
1069 : : else
1070 : : {
1071 : 11 : circleChordArea = M_PI * r2 * cov;
1072 : : }
1073 : :
1074 : 18 : if ( !circlePointLeftOfLine )
1075 : : {
1076 : 11 : sum += circleChordArea;
1077 : 11 : }
1078 : : else
1079 : : {
1080 : 7 : sum -= circleChordArea;
1081 : : }
1082 : 20 : }
1083 : 20 : }
1084 : :
1085 : 17 : bool QgsCircularString::hasCurvedSegments() const
1086 : : {
1087 : 17 : return true;
1088 : : }
1089 : :
1090 : 43 : double QgsCircularString::closestPointOnArc( double x1, double y1, double x2, double y2, double x3, double y3,
1091 : : const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon )
1092 : : {
1093 : : double radius, centerX, centerY;
1094 : 43 : QgsPoint pt1( x1, y1 );
1095 : 43 : QgsPoint pt2( x2, y2 );
1096 : 43 : QgsPoint pt3( x3, y3 );
1097 : :
1098 : 43 : QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
1099 : 43 : double angle = QgsGeometryUtils::ccwAngle( pt.y() - centerY, pt.x() - centerX );
1100 : 43 : double angle1 = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1101 : 43 : double angle2 = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
1102 : 43 : double angle3 = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
1103 : :
1104 : 43 : bool clockwise = QgsGeometryUtils::circleClockwise( angle1, angle2, angle3 );
1105 : :
1106 : 43 : if ( QgsGeometryUtils::angleOnCircle( angle, angle1, angle2, angle3 ) )
1107 : : {
1108 : : //get point on line center -> pt with distance radius
1109 : 29 : segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
1110 : :
1111 : : //vertexAfter
1112 : 29 : vertexAfter.vertex = QgsGeometryUtils::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
1113 : 29 : }
1114 : : else
1115 : : {
1116 : 14 : double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
1117 : 14 : double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
1118 : 14 : segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
1119 : 14 : vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
1120 : : }
1121 : :
1122 : 43 : double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
1123 : : //prevent rounding errors if the point is directly on the segment
1124 : 43 : if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
1125 : : {
1126 : 3 : segmentPt.setX( pt.x() );
1127 : 3 : segmentPt.setY( pt.y() );
1128 : 3 : sqrDistance = 0.0;
1129 : 3 : }
1130 : :
1131 : 43 : if ( leftOf )
1132 : : {
1133 : 43 : double sqrDistancePointToCenter = ( pt.x() - centerX ) * ( pt.x() - centerX ) + ( pt.y() - centerY ) * ( pt.y() - centerY );
1134 : 43 : *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 )
1135 : 32 : : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
1136 : 43 : }
1137 : :
1138 : 43 : return sqrDistance;
1139 : 43 : }
1140 : :
1141 : 14 : void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
1142 : : {
1143 : 14 : double xAfter = mX.at( after );
1144 : 14 : double yAfter = mY.at( after );
1145 : 14 : double xBefore = mX.at( before );
1146 : 14 : double yBefore = mY.at( before );
1147 : 14 : double xOnCircle = mX.at( pointOnCircle );
1148 : 14 : double yOnCircle = mY.at( pointOnCircle );
1149 : :
1150 : : double radius, centerX, centerY;
1151 : 14 : QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
1152 : :
1153 : 14 : double x = ( xAfter + xBefore ) / 2.0;
1154 : 14 : double y = ( yAfter + yBefore ) / 2.0;
1155 : :
1156 : 14 : QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
1157 : 14 : mX.insert( before, newVertex.x() );
1158 : 14 : mY.insert( before, newVertex.y() );
1159 : :
1160 : 14 : if ( is3D() )
1161 : : {
1162 : 4 : mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
1163 : 4 : }
1164 : 14 : if ( isMeasure() )
1165 : : {
1166 : 4 : mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
1167 : 4 : }
1168 : 14 : clearCache();
1169 : 14 : }
1170 : :
1171 : 87 : double QgsCircularString::vertexAngle( QgsVertexId vId ) const
1172 : : {
1173 : 87 : if ( numPoints() < 3 )
1174 : : {
1175 : : //undefined
1176 : 8 : return 0.0;
1177 : : }
1178 : :
1179 : 79 : int before = vId.vertex - 1;
1180 : 79 : int vertex = vId.vertex;
1181 : 79 : int after = vId.vertex + 1;
1182 : :
1183 : 79 : if ( vId.vertex % 2 != 0 ) // a curve vertex
1184 : : {
1185 : 28 : if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
1186 : : {
1187 : 56 : return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ),
1188 : 28 : QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1189 : : }
1190 : 0 : }
1191 : : else //a point vertex
1192 : : {
1193 : 51 : if ( vId.vertex == 0 )
1194 : : {
1195 : 40 : return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ),
1196 : 20 : QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1197 : : }
1198 : 31 : if ( vId.vertex >= numPoints() - 1 )
1199 : : {
1200 : 23 : int a = numPoints() - 3;
1201 : 23 : int b = numPoints() - 2;
1202 : 23 : int c = numPoints() - 1;
1203 : 46 : return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ),
1204 : 23 : QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1205 : : }
1206 : : else
1207 : : {
1208 : 8 : if ( vId.vertex + 2 > numPoints() - 1 )
1209 : : {
1210 : 0 : return 0.0;
1211 : : }
1212 : :
1213 : 8 : int vertex1 = vId.vertex - 2;
1214 : 8 : int vertex2 = vId.vertex - 1;
1215 : 8 : int vertex3 = vId.vertex;
1216 : 16 : double angle1 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1217 : 8 : QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1218 : 8 : int vertex4 = vId.vertex + 1;
1219 : 8 : int vertex5 = vId.vertex + 2;
1220 : 16 : double angle2 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1221 : 8 : QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1222 : 8 : return QgsGeometryUtils::averageAngle( angle1, angle2 );
1223 : : }
1224 : : }
1225 : 0 : return 0.0;
1226 : 87 : }
1227 : :
1228 : 56 : double QgsCircularString::segmentLength( QgsVertexId startVertex ) const
1229 : : {
1230 : 56 : if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1231 : 26 : return 0.0;
1232 : :
1233 : 30 : if ( startVertex.vertex % 2 == 1 )
1234 : 3 : return 0.0; // curve point?
1235 : :
1236 : 27 : double x1 = mX.at( startVertex.vertex );
1237 : 27 : double y1 = mY.at( startVertex.vertex );
1238 : 27 : double x2 = mX.at( startVertex.vertex + 1 );
1239 : 27 : double y2 = mY.at( startVertex.vertex + 1 );
1240 : 27 : double x3 = mX.at( startVertex.vertex + 2 );
1241 : 27 : double y3 = mY.at( startVertex.vertex + 2 );
1242 : 27 : return QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1243 : 56 : }
1244 : :
1245 : 5 : QgsCircularString *QgsCircularString::reversed() const
1246 : : {
1247 : 5 : QgsCircularString *copy = clone();
1248 : 5 : std::reverse( copy->mX.begin(), copy->mX.end() );
1249 : 5 : std::reverse( copy->mY.begin(), copy->mY.end() );
1250 : 5 : if ( is3D() )
1251 : : {
1252 : 4 : std::reverse( copy->mZ.begin(), copy->mZ.end() );
1253 : 4 : }
1254 : 5 : if ( isMeasure() )
1255 : : {
1256 : 4 : std::reverse( copy->mM.begin(), copy->mM.end() );
1257 : 4 : }
1258 : 5 : return copy;
1259 : : }
1260 : :
1261 : 17 : QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1262 : : {
1263 : 17 : if ( distance < 0 )
1264 : 1 : return nullptr;
1265 : :
1266 : 16 : double distanceTraversed = 0;
1267 : 16 : const int totalPoints = numPoints();
1268 : 16 : if ( totalPoints == 0 )
1269 : 1 : return nullptr;
1270 : :
1271 : 15 : QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1272 : 15 : if ( is3D() )
1273 : 13 : pointType = QgsWkbTypes::PointZ;
1274 : 15 : if ( isMeasure() )
1275 : 13 : pointType = QgsWkbTypes::addM( pointType );
1276 : :
1277 : 15 : const double *x = mX.constData();
1278 : 15 : const double *y = mY.constData();
1279 : 15 : const double *z = is3D() ? mZ.constData() : nullptr;
1280 : 15 : const double *m = isMeasure() ? mM.constData() : nullptr;
1281 : :
1282 : 15 : double prevX = *x++;
1283 : 15 : double prevY = *y++;
1284 : 15 : double prevZ = z ? *z++ : 0.0;
1285 : 15 : double prevM = m ? *m++ : 0.0;
1286 : :
1287 : 15 : if ( qgsDoubleNear( distance, 0.0 ) )
1288 : : {
1289 : 1 : return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1290 : : }
1291 : :
1292 : 19 : for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1293 : : {
1294 : 17 : double x1 = prevX;
1295 : 17 : double y1 = prevY;
1296 : 17 : double z1 = prevZ;
1297 : 17 : double m1 = prevM;
1298 : :
1299 : 17 : double x2 = *x++;
1300 : 17 : double y2 = *y++;
1301 : 17 : double z2 = z ? *z++ : 0.0;
1302 : 17 : double m2 = m ? *m++ : 0.0;
1303 : :
1304 : 17 : double x3 = *x++;
1305 : 17 : double y3 = *y++;
1306 : 17 : double z3 = z ? *z++ : 0.0;
1307 : 17 : double m3 = m ? *m++ : 0.0;
1308 : :
1309 : 17 : const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1310 : 17 : if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1311 : : {
1312 : : // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1313 : 12 : const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1314 : 24 : return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1315 : 12 : QgsPoint( pointType, x2, y2, z2, m2 ),
1316 : 12 : QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1317 : : }
1318 : :
1319 : 5 : distanceTraversed += segmentLength;
1320 : :
1321 : 5 : prevX = x3;
1322 : 5 : prevY = y3;
1323 : 5 : prevZ = z3;
1324 : 5 : prevM = m3;
1325 : 5 : }
1326 : :
1327 : 2 : return nullptr;
1328 : 17 : }
1329 : :
1330 : 26 : QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1331 : : {
1332 : 26 : if ( startDistance < 0 && endDistance < 0 )
1333 : 1 : return createEmptyWithSameType();
1334 : :
1335 : 25 : endDistance = std::max( startDistance, endDistance );
1336 : :
1337 : 25 : const int totalPoints = numPoints();
1338 : 25 : if ( totalPoints == 0 )
1339 : 1 : return clone();
1340 : :
1341 : 24 : QVector< QgsPoint > substringPoints;
1342 : 24 : substringPoints.reserve( totalPoints );
1343 : :
1344 : 24 : QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1345 : 24 : if ( is3D() )
1346 : 20 : pointType = QgsWkbTypes::PointZ;
1347 : 24 : if ( isMeasure() )
1348 : 20 : pointType = QgsWkbTypes::addM( pointType );
1349 : :
1350 : 24 : const double *x = mX.constData();
1351 : 24 : const double *y = mY.constData();
1352 : 24 : const double *z = is3D() ? mZ.constData() : nullptr;
1353 : 24 : const double *m = isMeasure() ? mM.constData() : nullptr;
1354 : :
1355 : 24 : double distanceTraversed = 0;
1356 : 24 : double prevX = *x++;
1357 : 24 : double prevY = *y++;
1358 : 24 : double prevZ = z ? *z++ : 0.0;
1359 : 24 : double prevM = m ? *m++ : 0.0;
1360 : 24 : bool foundStart = false;
1361 : :
1362 : 24 : if ( startDistance < 0 )
1363 : 8 : startDistance = 0;
1364 : :
1365 : 44 : for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1366 : : {
1367 : 33 : double x1 = prevX;
1368 : 33 : double y1 = prevY;
1369 : 33 : double z1 = prevZ;
1370 : 33 : double m1 = prevM;
1371 : :
1372 : 33 : double x2 = *x++;
1373 : 33 : double y2 = *y++;
1374 : 33 : double z2 = z ? *z++ : 0.0;
1375 : 33 : double m2 = m ? *m++ : 0.0;
1376 : :
1377 : 33 : double x3 = *x++;
1378 : 33 : double y3 = *y++;
1379 : 33 : double z3 = z ? *z++ : 0.0;
1380 : 33 : double m3 = m ? *m++ : 0.0;
1381 : :
1382 : 33 : bool addedSegmentEnd = false;
1383 : 33 : const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1384 : 33 : if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1385 : : {
1386 : : // start point falls on this segment
1387 : 23 : const double distanceToStart = startDistance - distanceTraversed;
1388 : 46 : const QgsPoint startPoint = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1389 : 23 : QgsPoint( pointType, x2, y2, z2, m2 ),
1390 : 23 : QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1391 : :
1392 : : // does end point also fall on this segment?
1393 : 23 : const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1394 : 23 : if ( endPointOnSegment )
1395 : : {
1396 : 10 : const double distanceToEnd = endDistance - distanceTraversed;
1397 : 10 : const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1398 : 20 : substringPoints << startPoint
1399 : 20 : << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1400 : 10 : QgsPoint( pointType, x2, y2, z2, m2 ),
1401 : 10 : QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1402 : 20 : << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1403 : 10 : QgsPoint( pointType, x2, y2, z2, m2 ),
1404 : 10 : QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1405 : 10 : addedSegmentEnd = true;
1406 : 10 : }
1407 : : else
1408 : : {
1409 : 13 : const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1410 : 26 : substringPoints << startPoint
1411 : 26 : << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1412 : 13 : QgsPoint( pointType, x2, y2, z2, m2 ),
1413 : 13 : QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1414 : 13 : << QgsPoint( pointType, x3, y3, z3, m3 );
1415 : 13 : addedSegmentEnd = true;
1416 : : }
1417 : 23 : foundStart = true;
1418 : 23 : }
1419 : 33 : if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1420 : : {
1421 : : // end point falls on this segment
1422 : 2 : const double distanceToEnd = endDistance - distanceTraversed;
1423 : : // add mid point, at half way along this arc, then add the interpolated end point
1424 : 6 : substringPoints << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1425 : 2 : QgsPoint( pointType, x2, y2, z2, m2 ),
1426 : 2 : QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1427 : :
1428 : 4 : << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1429 : 2 : QgsPoint( pointType, x2, y2, z2, m2 ),
1430 : 2 : QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1431 : 2 : }
1432 : 31 : else if ( !addedSegmentEnd && foundStart )
1433 : : {
1434 : 8 : substringPoints << QgsPoint( pointType, x2, y2, z2, m2 )
1435 : 4 : << QgsPoint( pointType, x3, y3, z3, m3 );
1436 : 4 : }
1437 : :
1438 : 33 : prevX = x3;
1439 : 33 : prevY = y3;
1440 : 33 : prevZ = z3;
1441 : 33 : prevM = m3;
1442 : 33 : distanceTraversed += segmentLength;
1443 : 33 : if ( distanceTraversed >= endDistance )
1444 : 13 : break;
1445 : 20 : }
1446 : :
1447 : : // start point is the last node
1448 : 24 : if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1449 : : {
1450 : 0 : substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1451 : 0 : << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1452 : 0 : << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1453 : 0 : }
1454 : :
1455 : 24 : std::unique_ptr< QgsCircularString > result = std::make_unique< QgsCircularString >();
1456 : 24 : result->setPoints( substringPoints );
1457 : 24 : return result.release();
1458 : 26 : }
1459 : :
1460 : 28 : bool QgsCircularString::addZValue( double zValue )
1461 : : {
1462 : 28 : if ( QgsWkbTypes::hasZ( mWkbType ) )
1463 : 1 : return false;
1464 : :
1465 : 27 : clearCache();
1466 : 27 : mWkbType = QgsWkbTypes::addZ( mWkbType );
1467 : :
1468 : 27 : int nPoints = numPoints();
1469 : 27 : mZ.clear();
1470 : 27 : mZ.reserve( nPoints );
1471 : 92 : for ( int i = 0; i < nPoints; ++i )
1472 : : {
1473 : 65 : mZ << zValue;
1474 : 65 : }
1475 : 27 : return true;
1476 : 28 : }
1477 : :
1478 : 28 : bool QgsCircularString::addMValue( double mValue )
1479 : : {
1480 : 28 : if ( QgsWkbTypes::hasM( mWkbType ) )
1481 : 1 : return false;
1482 : :
1483 : 27 : clearCache();
1484 : 27 : mWkbType = QgsWkbTypes::addM( mWkbType );
1485 : :
1486 : 27 : int nPoints = numPoints();
1487 : 27 : mM.clear();
1488 : 27 : mM.reserve( nPoints );
1489 : 96 : for ( int i = 0; i < nPoints; ++i )
1490 : : {
1491 : 69 : mM << mValue;
1492 : 69 : }
1493 : 27 : return true;
1494 : 28 : }
1495 : :
1496 : 41 : bool QgsCircularString::dropZValue()
1497 : : {
1498 : 41 : if ( !QgsWkbTypes::hasZ( mWkbType ) )
1499 : 19 : return false;
1500 : :
1501 : 22 : clearCache();
1502 : :
1503 : 22 : mWkbType = QgsWkbTypes::dropZ( mWkbType );
1504 : 22 : mZ.clear();
1505 : 22 : return true;
1506 : 41 : }
1507 : :
1508 : 45 : bool QgsCircularString::dropMValue()
1509 : : {
1510 : 45 : if ( !QgsWkbTypes::hasM( mWkbType ) )
1511 : 25 : return false;
1512 : :
1513 : 20 : clearCache();
1514 : :
1515 : 20 : mWkbType = QgsWkbTypes::dropM( mWkbType );
1516 : 20 : mM.clear();
1517 : 20 : return true;
1518 : 45 : }
1519 : :
1520 : 4 : void QgsCircularString::swapXy()
1521 : : {
1522 : 4 : std::swap( mX, mY );
1523 : 4 : clearCache();
1524 : 4 : }
|