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