1 /***************************************************************************
2 qgscurve.cpp
3 --------------
4 begin : November 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 <memory>
19
20 #include "qgscurve.h"
21 #include "qgslinestring.h"
22 #include "qgspoint.h"
23 #include "qgsmultipoint.h"
24 #include "qgsgeos.h"
25 #include "qgsvertexid.h"
26
operator ==(const QgsAbstractGeometry & other) const27 bool QgsCurve::operator==( const QgsAbstractGeometry &other ) const
28 {
29 const QgsCurve *otherCurve = qgsgeometry_cast< const QgsCurve * >( &other );
30 if ( !otherCurve )
31 return false;
32
33 return equals( *otherCurve );
34 }
35
operator !=(const QgsAbstractGeometry & other) const36 bool QgsCurve::operator!=( const QgsAbstractGeometry &other ) const
37 {
38 return !operator==( other );
39 }
40
isClosed2D() const41 bool QgsCurve::isClosed2D() const
42 {
43 if ( numPoints() == 0 )
44 return false;
45
46 //don't consider M-coordinates when testing closedness
47 const QgsPoint start = startPoint();
48 const QgsPoint end = endPoint();
49
50 return qgsDoubleNear( start.x(), end.x() ) &&
51 qgsDoubleNear( start.y(), end.y() );
52 }
isClosed() const53 bool QgsCurve::isClosed() const
54 {
55 bool closed = isClosed2D();
56 if ( is3D() && closed )
57 {
58 const QgsPoint start = startPoint();
59 const QgsPoint end = endPoint();
60 closed &= qgsDoubleNear( start.z(), end.z() ) || ( std::isnan( start.z() ) && std::isnan( end.z() ) );
61 }
62 return closed;
63 }
64
isRing() const65 bool QgsCurve::isRing() const
66 {
67 return ( isClosed() && numPoints() >= 4 );
68 }
69
asQPainterPath() const70 QPainterPath QgsCurve::asQPainterPath() const
71 {
72 QPainterPath p;
73 addToPainterPath( p );
74 return p;
75 }
76
coordinateSequence() const77 QgsCoordinateSequence QgsCurve::coordinateSequence() const
78 {
79 QgsCoordinateSequence sequence;
80 sequence.append( QgsRingSequence() );
81 sequence.back().append( QgsPointSequence() );
82 points( sequence.back().back() );
83
84 return sequence;
85 }
86
nextVertex(QgsVertexId & id,QgsPoint & vertex) const87 bool QgsCurve::nextVertex( QgsVertexId &id, QgsPoint &vertex ) const
88 {
89 if ( id.vertex < 0 )
90 {
91 id.vertex = 0;
92 if ( id.part < 0 )
93 {
94 id.part = 0;
95 }
96 if ( id.ring < 0 )
97 {
98 id.ring = 0;
99 }
100 }
101 else
102 {
103 if ( id.vertex + 1 >= numPoints() )
104 {
105 return false;
106 }
107 ++id.vertex;
108 }
109 return pointAt( id.vertex, vertex, id.type );
110 }
111
adjacentVertices(QgsVertexId vertex,QgsVertexId & previousVertex,QgsVertexId & nextVertex) const112 void QgsCurve::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const
113 {
114 const int n = numPoints();
115 if ( vertex.vertex < 0 || vertex.vertex >= n )
116 {
117 previousVertex = QgsVertexId();
118 nextVertex = QgsVertexId();
119 return;
120 }
121
122 if ( vertex.vertex == 0 )
123 {
124 previousVertex = QgsVertexId();
125 }
126 else
127 {
128 previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 );
129 }
130 if ( vertex.vertex == n - 1 )
131 {
132 nextVertex = QgsVertexId();
133 }
134 else
135 {
136 nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 );
137 }
138 }
139
vertexNumberFromVertexId(QgsVertexId id) const140 int QgsCurve::vertexNumberFromVertexId( QgsVertexId id ) const
141 {
142 if ( id.part != 0 || id.ring != 0 )
143 return -1;
144 if ( id.vertex < 0 || id.vertex >= numPoints() )
145 return -1;
146 return id.vertex;
147 }
148
boundary() const149 QgsAbstractGeometry *QgsCurve::boundary() const
150 {
151 if ( isEmpty() )
152 return nullptr;
153
154 if ( isClosed() )
155 return nullptr;
156
157 QgsMultiPoint *multiPoint = new QgsMultiPoint();
158 multiPoint->reserve( 2 );
159 multiPoint->addGeometry( new QgsPoint( startPoint() ) );
160 multiPoint->addGeometry( new QgsPoint( endPoint() ) );
161 return multiPoint;
162 }
163
asKml(int precision) const164 QString QgsCurve::asKml( int precision ) const
165 {
166 std::unique_ptr<QgsLineString> lineString( curveToLine() );
167 if ( !lineString )
168 {
169 return QString();
170 }
171 QString kml = lineString->asKml( precision );
172 return kml;
173 }
174
segmentize(double tolerance,SegmentationToleranceType toleranceType) const175 QgsCurve *QgsCurve::segmentize( double tolerance, SegmentationToleranceType toleranceType ) const
176 {
177 return curveToLine( tolerance, toleranceType );
178 }
179
vertexCount(int part,int ring) const180 int QgsCurve::vertexCount( int part, int ring ) const
181 {
182 Q_UNUSED( part )
183 Q_UNUSED( ring )
184 return numPoints();
185 }
186
ringCount(int part) const187 int QgsCurve::ringCount( int part ) const
188 {
189 Q_UNUSED( part )
190 return numPoints() > 0 ? 1 : 0;
191 }
192
partCount() const193 int QgsCurve::partCount() const
194 {
195 return numPoints() > 0 ? 1 : 0;
196 }
197
vertexAt(QgsVertexId id) const198 QgsPoint QgsCurve::vertexAt( QgsVertexId id ) const
199 {
200 QgsPoint v;
201 Qgis::VertexType type;
202 pointAt( id.vertex, v, type );
203 return v;
204 }
205
toCurveType() const206 QgsCurve *QgsCurve::toCurveType() const
207 {
208 return clone();
209 }
210
normalize()211 void QgsCurve::normalize()
212 {
213 if ( isEmpty() )
214 return;
215
216 if ( !isClosed() )
217 {
218 return;
219 }
220
221 int minCoordinateIndex = 0;
222 QgsPoint minCoord;
223 int i = 0;
224 for ( auto it = vertices_begin(); it != vertices_end(); ++it )
225 {
226 const QgsPoint vertex = *it;
227 if ( minCoord.isEmpty() || minCoord.compareTo( &vertex ) > 0 )
228 {
229 minCoord = vertex;
230 minCoordinateIndex = i;
231 }
232 i++;
233 }
234
235 scroll( minCoordinateIndex );
236 }
237
boundingBox() const238 QgsRectangle QgsCurve::boundingBox() const
239 {
240 if ( mBoundingBox.isNull() )
241 {
242 mBoundingBox = calculateBoundingBox();
243 }
244 return mBoundingBox;
245 }
246
isValid(QString & error,Qgis::GeometryValidityFlags flags) const247 bool QgsCurve::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
248 {
249 if ( flags == 0 && mHasCachedValidity )
250 {
251 // use cached validity results
252 error = mValidityFailureReason;
253 return error.isEmpty();
254 }
255
256 const QgsGeos geos( this );
257 const bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
258 if ( flags == 0 )
259 {
260 mValidityFailureReason = !res ? error : QString();
261 mHasCachedValidity = true;
262 }
263 return res;
264 }
265
asQPolygonF() const266 QPolygonF QgsCurve::asQPolygonF() const
267 {
268 std::unique_ptr< QgsLineString > segmentized( curveToLine() );
269 return segmentized->asQPolygonF();
270 }
271
straightDistance2d() const272 double QgsCurve::straightDistance2d() const
273 {
274 return startPoint().distance( endPoint() );
275 }
276
sinuosity() const277 double QgsCurve::sinuosity() const
278 {
279 const double d = straightDistance2d();
280 if ( qgsDoubleNear( d, 0.0 ) )
281 return std::numeric_limits<double>::quiet_NaN();
282
283 return length() / d;
284 }
285
orientation() const286 QgsCurve::Orientation QgsCurve::orientation() const
287 {
288 double a = 0;
289 sumUpArea( a );
290 return a < 0 ? Clockwise : CounterClockwise;
291 }
292
clearCache() const293 void QgsCurve::clearCache() const
294 {
295 mBoundingBox = QgsRectangle();
296 mHasCachedValidity = false;
297 mValidityFailureReason.clear();
298 QgsAbstractGeometry::clearCache();
299 }
300
childCount() const301 int QgsCurve::childCount() const
302 {
303 return numPoints();
304 }
305
childPoint(int index) const306 QgsPoint QgsCurve::childPoint( int index ) const
307 {
308 QgsPoint point;
309 Qgis::VertexType type;
310 const bool res = pointAt( index, point, type );
311 Q_ASSERT( res );
312 Q_UNUSED( res )
313 return point;
314 }
315
snapToGridPrivate(double hSpacing,double vSpacing,double dSpacing,double mSpacing,const QVector<double> & srcX,const QVector<double> & srcY,const QVector<double> & srcZ,const QVector<double> & srcM,QVector<double> & outX,QVector<double> & outY,QVector<double> & outZ,QVector<double> & outM) const316 bool QgsCurve::snapToGridPrivate( double hSpacing, double vSpacing, double dSpacing, double mSpacing,
317 const QVector<double> &srcX, const QVector<double> &srcY, const QVector<double> &srcZ, const QVector<double> &srcM,
318 QVector<double> &outX, QVector<double> &outY, QVector<double> &outZ, QVector<double> &outM ) const
319 {
320 const int length = numPoints();
321
322 if ( length <= 0 )
323 return false;
324
325 const bool hasZ = is3D();
326 const bool hasM = isMeasure();
327
328 // helper functions
329 auto roundVertex = [hSpacing, vSpacing, dSpacing, mSpacing, hasZ, hasM, &srcX, &srcY, &srcZ, &srcM]( QgsPoint & out, int i )
330 {
331 if ( hSpacing > 0 )
332 out.setX( std::round( srcX.at( i ) / hSpacing ) * hSpacing );
333 else
334 out.setX( srcX.at( i ) );
335
336 if ( vSpacing > 0 )
337 out.setY( std::round( srcY.at( i ) / vSpacing ) * vSpacing );
338 else
339 out.setY( srcY.at( i ) );
340
341 if ( hasZ )
342 {
343 if ( dSpacing > 0 )
344 out.setZ( std::round( srcZ.at( i ) / dSpacing ) * dSpacing );
345 else
346 out.setZ( srcZ.at( i ) );
347 }
348
349 if ( hasM )
350 {
351 if ( mSpacing > 0 )
352 out.setM( std::round( srcM.at( i ) / mSpacing ) * mSpacing );
353 else
354 out.setM( srcM.at( i ) );
355 }
356 };
357
358
359 auto append = [hasZ, hasM, &outX, &outY, &outM, &outZ]( QgsPoint const & point )
360 {
361 outX.append( point.x() );
362
363 outY.append( point.y() );
364
365 if ( hasZ )
366 outZ.append( point.z() );
367
368 if ( hasM )
369 outM.append( point.m() );
370 };
371
372 auto isPointEqual = [dSpacing, mSpacing, hasZ, hasM]( const QgsPoint & a, const QgsPoint & b )
373 {
374 return ( a.x() == b.x() )
375 && ( a.y() == b.y() )
376 && ( !hasZ || dSpacing <= 0 || a.z() == b.z() )
377 && ( !hasM || mSpacing <= 0 || a.m() == b.m() );
378 };
379
380 // temporary values
381 const QgsWkbTypes::Type pointType = QgsWkbTypes::zmType( QgsWkbTypes::Point, hasZ, hasM );
382 QgsPoint last( pointType );
383 QgsPoint current( pointType );
384
385 // Actual code (what does all the work)
386 roundVertex( last, 0 );
387 append( last );
388
389 for ( int i = 1; i < length; ++i )
390 {
391 roundVertex( current, i );
392 if ( !isPointEqual( current, last ) )
393 {
394 append( current );
395 last = current;
396 }
397 }
398
399 // if it's not closed, with 2 points you get a correct line
400 // if it is, you need at least 4 (3 + the vertex that closes)
401 if ( outX.length() < 2 || ( isClosed() && outX.length() < 4 ) )
402 return false;
403
404 return true;
405 }
406