1 /*
2  *   libpal - Automated Placement of Labels Library
3  *
4  *   Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5  *                      University of Applied Sciences, Western Switzerland
6  *                      http://www.hes-so.ch
7  *
8  *   Contact:
9  *      maxence.laurent <at> heig-vd <dot> ch
10  *    or
11  *      eric.taillard <at> heig-vd <dot> ch
12  *
13  * This file is part of libpal.
14  *
15  * libpal is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation, either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * libpal is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with libpal.  If not, see <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 #include "pointset.h"
31 #include "util.h"
32 #include "pal.h"
33 #include "geomfunction.h"
34 #include "qgsgeos.h"
35 #include "qgsmessagelog.h"
36 #include "qgsgeometryutils.h"
37 #include <qglobal.h>
38 
39 using namespace pal;
40 
PointSet()41 PointSet::PointSet()
42 {
43   nbPoints = 0;
44   type = -1;
45 }
46 
PointSet(int nbPoints,double * x,double * y)47 PointSet::PointSet( int nbPoints, double *x, double *y )
48   : nbPoints( nbPoints )
49   , type( GEOS_POLYGON )
50 {
51   this->x.resize( nbPoints );
52   this->y.resize( nbPoints );
53   int i;
54 
55   for ( i = 0; i < nbPoints; i++ )
56   {
57     this->x[i] = x[i];
58     this->y[i] = y[i];
59   }
60 
61 }
62 
PointSet(double aX,double aY)63 PointSet::PointSet( double aX, double aY )
64   : type( GEOS_POINT )
65   , xmin( aX )
66   , xmax( aY )
67   , ymin( aX )
68   , ymax( aY )
69 {
70   nbPoints = 1;
71   x.resize( 1 );
72   y.resize( 1 );
73   x[0] = aX;
74   y[0] = aY;
75 }
76 
PointSet(const PointSet & ps)77 PointSet::PointSet( const PointSet &ps )
78   : xmin( ps.xmin )
79   , xmax( ps.xmax )
80   , ymin( ps.ymin )
81   , ymax( ps.ymax )
82 {
83   nbPoints = ps.nbPoints;
84   x = ps.x;
85   y = ps.y;
86 
87   convexHull = ps.convexHull;
88 
89   type = ps.type;
90 
91   holeOf = ps.holeOf;
92 
93   if ( ps.mGeos )
94   {
95     mGeos = GEOSGeom_clone_r( QgsGeos::getGEOSHandler(), ps.mGeos );
96     mOwnsGeom = true;
97   }
98 }
99 
createGeosGeom() const100 void PointSet::createGeosGeom() const
101 {
102   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
103 
104   bool needClose = false;
105   if ( type == GEOS_POLYGON && ( !qgsDoubleNear( x[0], x[ nbPoints - 1] ) || !qgsDoubleNear( y[0], y[ nbPoints - 1 ] ) ) )
106   {
107     needClose = true;
108   }
109 
110   GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, nbPoints + ( needClose ? 1 : 0 ), 2 );
111   for ( int i = 0; i < nbPoints; ++i )
112   {
113 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
114     GEOSCoordSeq_setXY_r( geosctxt, coord, i, x[i], y[i] );
115 #else
116     GEOSCoordSeq_setX_r( geosctxt, coord, i, x[i] );
117     GEOSCoordSeq_setY_r( geosctxt, coord, i, y[i] );
118 #endif
119   }
120 
121   //close ring if needed
122   if ( needClose )
123   {
124 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
125     GEOSCoordSeq_setXY_r( geosctxt, coord, nbPoints, x[0], y[0] );
126 #else
127     GEOSCoordSeq_setX_r( geosctxt, coord, nbPoints, x[0] );
128     GEOSCoordSeq_setY_r( geosctxt, coord, nbPoints, y[0] );
129 #endif
130   }
131 
132   switch ( type )
133   {
134     case GEOS_POLYGON:
135       mGeos = GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 );
136       break;
137 
138     case GEOS_LINESTRING:
139       mGeos = GEOSGeom_createLineString_r( geosctxt, coord );
140       break;
141 
142     case GEOS_POINT:
143       mGeos = GEOSGeom_createPoint_r( geosctxt, coord );
144       break;
145   }
146 
147   mOwnsGeom = true;
148 }
149 
preparedGeom() const150 const GEOSPreparedGeometry *PointSet::preparedGeom() const
151 {
152   if ( !mGeos )
153     createGeosGeom();
154 
155   if ( !mPreparedGeom )
156   {
157     mPreparedGeom = GEOSPrepare_r( QgsGeos::getGEOSHandler(), mGeos );
158   }
159   return mPreparedGeom;
160 }
161 
invalidateGeos()162 void PointSet::invalidateGeos()
163 {
164   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
165   if ( mOwnsGeom ) // delete old geometry if we own it
166     GEOSGeom_destroy_r( geosctxt, mGeos );
167   mOwnsGeom = false;
168   mGeos = nullptr;
169 
170   if ( mPreparedGeom )
171   {
172     GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
173     mPreparedGeom = nullptr;
174   }
175 
176   if ( mGeosPreparedBoundary )
177   {
178     GEOSPreparedGeom_destroy_r( geosctxt, mGeosPreparedBoundary );
179     mGeosPreparedBoundary = nullptr;
180   }
181 
182   if ( mMultipartPreparedGeos )
183   {
184     GEOSPreparedGeom_destroy_r( geosctxt, mMultipartPreparedGeos );
185     mMultipartPreparedGeos = nullptr;
186   }
187   if ( mMultipartGeos )
188   {
189     GEOSGeom_destroy_r( geosctxt, mMultipartGeos );
190     mMultipartGeos = nullptr;
191   }
192 
193   mLength = -1;
194   mArea = -1;
195 }
196 
~PointSet()197 PointSet::~PointSet()
198 {
199   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
200 
201   if ( mGeos && mOwnsGeom )
202   {
203     GEOSGeom_destroy_r( geosctxt, mGeos );
204     mGeos = nullptr;
205   }
206   GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
207 
208   if ( mGeosPreparedBoundary )
209   {
210     GEOSPreparedGeom_destroy_r( geosctxt, mGeosPreparedBoundary );
211     mGeosPreparedBoundary = nullptr;
212   }
213 
214   if ( mMultipartPreparedGeos )
215   {
216     GEOSPreparedGeom_destroy_r( geosctxt, mMultipartPreparedGeos );
217     mMultipartPreparedGeos = nullptr;
218   }
219   if ( mMultipartGeos )
220   {
221     GEOSGeom_destroy_r( geosctxt, mMultipartGeos );
222     mMultipartGeos = nullptr;
223   }
224 
225   deleteCoords();
226 }
227 
deleteCoords()228 void PointSet::deleteCoords()
229 {
230   x.clear();
231   y.clear();
232 }
233 
extractShape(int nbPtSh,int imin,int imax,int fps,int fpe,double fptx,double fpty)234 std::unique_ptr<PointSet> PointSet::extractShape( int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty )
235 {
236   int i, j;
237 
238   std::unique_ptr<PointSet> newShape = std::make_unique< PointSet >();
239   newShape->type = GEOS_POLYGON;
240   newShape->nbPoints = nbPtSh;
241   newShape->x.resize( newShape->nbPoints );
242   newShape->y.resize( newShape->nbPoints );
243 
244   // new shape # 1 from imin to imax
245   for ( j = 0, i = imin; i != ( imax + 1 ) % nbPoints; i = ( i + 1 ) % nbPoints, j++ )
246   {
247     newShape->x[j] = x[i];
248     newShape->y[j] = y[i];
249   }
250   // is the cutting point a new one ?
251   if ( fps != fpe )
252   {
253     // yes => so add it
254     newShape->x[j] = fptx;
255     newShape->y[j] = fpty;
256   }
257 
258   return newShape;
259 }
260 
clone() const261 std::unique_ptr<PointSet> PointSet::clone() const
262 {
263   return std::unique_ptr< PointSet>( new PointSet( *this ) );
264 }
265 
containsPoint(double x,double y) const266 bool PointSet::containsPoint( double x, double y ) const
267 {
268   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
269   try
270   {
271 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
272     geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, x, y ) );
273 #else
274     GEOSCoordSequence *seq = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
275     GEOSCoordSeq_setX_r( geosctxt, seq, 0, x );
276     GEOSCoordSeq_setY_r( geosctxt, seq, 0, y );
277     geos::unique_ptr point( GEOSGeom_createPoint_r( geosctxt, seq ) );
278 #endif
279     const bool result = ( GEOSPreparedContainsProperly_r( geosctxt, preparedGeom(), point.get() ) == 1 );
280 
281     return result;
282   }
283   catch ( GEOSException &e )
284   {
285     qWarning( "GEOS exception: %s", e.what() );
286     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
287     return false;
288   }
289 
290 }
291 
containsLabelCandidate(double x,double y,double width,double height,double alpha) const292 bool PointSet::containsLabelCandidate( double x, double y, double width, double height, double alpha ) const
293 {
294   return GeomFunction::containsCandidate( preparedGeom(), x, y, width, height, alpha );
295 }
296 
splitPolygons(PointSet * inputShape,double labelWidth,double labelHeight)297 QLinkedList<PointSet *> PointSet::splitPolygons( PointSet *inputShape, double labelWidth, double labelHeight )
298 {
299   int j;
300 
301   double bestArea = 0;
302 
303   double b;
304 
305   int holeS = -1;  // hole start and end points
306   int holeE = -1;
307 
308   int retainedPt = -1;
309 
310   const double labelArea = labelWidth * labelHeight;
311 
312   QLinkedList<PointSet *> inputShapes;
313   inputShapes.push_back( inputShape );
314   QLinkedList<PointSet *> outputShapes;
315 
316   while ( !inputShapes.isEmpty() )
317   {
318     PointSet *shape = inputShapes.takeFirst();
319 
320     const std::vector< double > &x = shape->x;
321     const std::vector< double > &y = shape->y;
322     const int nbp = shape->nbPoints;
323     std::vector< int > pts( nbp );
324     for ( int i = 0; i < nbp; i++ )
325     {
326       pts[i] = i;
327     }
328 
329     // compute convex hull
330     shape->convexHull = GeomFunction::convexHullId( pts, x, y );
331 
332     bestArea = 0;
333     retainedPt = -1;
334 
335     // lookup for a hole
336     for ( std::size_t ihs = 0; ihs < shape->convexHull.size(); ihs++ )
337     {
338       // ihs->ihn => cHull'seg
339       const std::size_t ihn = ( ihs + 1 ) % shape->convexHull.size();
340 
341       const int ips = shape->convexHull[ihs];
342       const int ipn = ( ips + 1 ) % nbp;
343       if ( ipn != shape->convexHull[ihn] ) // next point on shape is not the next point on cHull => there is a hole here !
344       {
345         double bestcp = 0;
346         int pt = -1;
347         // lookup for the deepest point in the hole
348         for ( int i = ips; i != shape->convexHull[ihn]; i = ( i + 1 ) % nbp )
349         {
350           const double cp = std::fabs( GeomFunction::cross_product( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
351                                        x[shape->convexHull[ihn]], y[shape->convexHull[ihn]],
352                                        x[i], y[i] ) );
353           if ( cp - bestcp > EPSILON )
354           {
355             bestcp = cp;
356             pt = i;
357           }
358         }
359 
360         if ( pt  != -1 )
361         {
362           // compute the ihs->ihn->pt triangle's area
363           const double base = GeomFunction::dist_euc2d( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
364                               x[shape->convexHull[ihn]], y[shape->convexHull[ihn]] );
365 
366           b = GeomFunction::dist_euc2d( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
367                                         x[pt], y[pt] );
368 
369           const double c = GeomFunction::dist_euc2d( x[shape->convexHull[ihn]], y[shape->convexHull[ihn]],
370                            x[pt], y[pt] );
371 
372           const double s = ( base + b + c ) / 2; // s = half perimeter
373           double area = s * ( s - base ) * ( s - b ) * ( s - c );
374           if ( area < 0 )
375             area = -area;
376 
377           // retain the biggest area
378           if ( area - bestArea > EPSILON )
379           {
380             bestArea = area;
381             retainedPt = pt;
382             holeS = ihs;
383             holeE = ihn;
384           }
385         }
386       }
387     }
388 
389     // we have a hole, its area, and the deppest point in hole
390     // we're going to find the second point to cup the shape
391     // holeS = hole starting point
392     // holeE = hole ending point
393     // retainedPt = deppest point in hole
394     // bestArea = area of triangle HoleS->holeE->retainedPoint
395     bestArea = std::sqrt( bestArea );
396     double cx, cy, dx, dy, ex, ey, fx, fy, seg_length, ptx = 0, pty = 0, fptx = 0, fpty = 0;
397     int ps = -1, pe = -1, fps = -1, fpe = -1;
398     if ( retainedPt >= 0 && bestArea > labelArea ) // there is a hole so we'll cut the shape in two new shape (only if hole area is bigger than twice labelArea)
399     {
400       double c = std::numeric_limits<double>::max();
401 
402       // iterate on all shape points except points which are in the hole
403       bool isValid;
404       int k, l;
405       for ( int i = ( shape->convexHull[holeE] + 1 ) % nbp; i != ( shape->convexHull[holeS] - 1 + nbp ) % nbp; i = j )
406       {
407         j = ( i + 1 ) % nbp; // i->j is shape segment not in hole
408 
409         // compute distance between retainedPoint and segment
410         // whether perpendicular distance (if retaindPoint is fronting segment i->j)
411         // or distance between retainedPt and i or j (choose the nearest)
412         seg_length = GeomFunction::dist_euc2d( x[i], y[i], x[j], y[j] );
413         cx = ( x[i] + x[j] ) / 2.0;
414         cy = ( y[i] + y[j] ) / 2.0;
415         dx = cy - y[i];
416         dy = cx - x[i];
417 
418         ex = cx - dx;
419         ey = cy + dy;
420         fx = cx + dx;
421         fy = cy - dy;
422 
423         if ( seg_length < EPSILON || std::fabs( ( b = GeomFunction::cross_product( ex, ey, fx, fy, x[retainedPt], y[retainedPt] ) / ( seg_length ) ) ) > ( seg_length / 2 ) )   // retainedPt is not fronting i->j
424         {
425           if ( ( ex = GeomFunction::dist_euc2d_sq( x[i], y[i], x[retainedPt], y[retainedPt] ) ) < ( ey = GeomFunction::dist_euc2d_sq( x[j], y[j], x[retainedPt], y[retainedPt] ) ) )
426           {
427             b = ex;
428             ps = i;
429             pe = i;
430           }
431           else
432           {
433             b = ey;
434             ps = j;
435             pe = j;
436           }
437         }
438         else   // point fronting i->j => compute pependicular distance  => create a new point
439         {
440           b = GeomFunction::cross_product( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt] ) / seg_length;
441           b *= b;
442           ps = i;
443           pe = j;
444 
445           if ( !GeomFunction::computeLineIntersection( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt], x[retainedPt] - dx, y[retainedPt] + dy, &ptx, &pty ) )
446           {
447             //error - it should intersect the line
448           }
449         }
450 
451         isValid = true;
452         double pointX, pointY;
453         if ( ps == pe )
454         {
455           pointX = x[pe];
456           pointY = y[pe];
457         }
458         else
459         {
460           pointX = ptx;
461           pointY = pty;
462         }
463 
464         for ( k = shape->convexHull[holeS]; k != shape->convexHull[holeE]; k = ( k + 1 ) % nbp )
465         {
466           l = ( k + 1 ) % nbp;
467           if ( GeomFunction::isSegIntersects( x[retainedPt], y[retainedPt], pointX, pointY, x[k], y[k], x[l], y[l] ) )
468           {
469             isValid = false;
470             break;
471           }
472         }
473 
474 
475         if ( isValid && b < c )
476         {
477           c = b;
478           fps = ps;
479           fpe = pe;
480           fptx = ptx;
481           fpty = pty;
482         }
483       }  // for point which are not in hole
484 
485       // we will cut the shapeu in two new shapes, one from [retainedPoint] to [newPoint] and one form [newPoint] to [retainedPoint]
486       const int imin = retainedPt;
487       int imax = ( ( ( fps < retainedPt && fpe < retainedPt ) || ( fps > retainedPt && fpe > retainedPt ) ) ? std::min( fps, fpe ) : std::max( fps, fpe ) );
488 
489       int nbPtSh1, nbPtSh2; // how many points in new shapes ?
490       if ( imax > imin )
491         nbPtSh1 = imax - imin + 1 + ( fpe != fps );
492       else
493         nbPtSh1 = imax + nbp - imin + 1 + ( fpe != fps );
494 
495       if ( ( imax == fps ? fpe : fps ) < imin )
496         nbPtSh2 = imin - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
497       else
498         nbPtSh2 = imin + nbp - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
499 
500       if ( retainedPt == -1 || fps == -1 || fpe == -1 )
501       {
502         if ( shape->parent )
503           delete shape;
504       }
505       // check for useless splitting
506       else if ( imax == imin || nbPtSh1 <= 2 || nbPtSh2 <= 2 || nbPtSh1 == nbp  || nbPtSh2 == nbp )
507       {
508         outputShapes.append( shape );
509       }
510       else
511       {
512 
513         PointSet *newShape = shape->extractShape( nbPtSh1, imin, imax, fps, fpe, fptx, fpty ).release();
514 
515         if ( shape->parent )
516           newShape->parent = shape->parent;
517         else
518           newShape->parent = shape;
519 
520         inputShapes.append( newShape );
521 
522         if ( imax == fps )
523           imax = fpe;
524         else
525           imax = fps;
526 
527         newShape = shape->extractShape( nbPtSh2, imax, imin, fps, fpe, fptx, fpty ).release();
528 
529         if ( shape->parent )
530           newShape->parent = shape->parent;
531         else
532           newShape->parent = shape;
533 
534         inputShapes.append( newShape );
535 
536         if ( shape->parent )
537           delete shape;
538       }
539     }
540     else
541     {
542       outputShapes.append( shape );
543     }
544   }
545   return outputShapes;
546 }
547 
offsetCurveByDistance(double distance)548 void PointSet::offsetCurveByDistance( double distance )
549 {
550   if ( !mGeos )
551     createGeosGeom();
552 
553   if ( !mGeos || type != GEOS_LINESTRING )
554     return;
555 
556   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
557   GEOSGeometry *newGeos;
558   try
559   {
560     newGeos = GEOSOffsetCurve_r( geosctxt, mGeos, distance, 0, GEOSBUF_JOIN_MITRE, 2 );
561     if ( !newGeos )
562       return;
563 
564     // happens sometime, if the offset curve self-intersects
565     if ( GEOSGeomTypeId_r( geosctxt, newGeos ) == GEOS_MULTILINESTRING )
566     {
567       // we keep the longest part
568       const int nParts = GEOSGetNumGeometries_r( geosctxt, newGeos );
569       double maximumLength = -1;
570       const GEOSGeometry *longestPart = nullptr;
571       for ( int i = 0; i < nParts; ++i )
572       {
573         const GEOSGeometry *part = GEOSGetGeometryN_r( geosctxt, newGeos, i );
574         double partLength = -1;
575         if ( GEOSLength_r( geosctxt, part, &partLength ) == 1 )
576         {
577           if ( partLength > maximumLength )
578           {
579             maximumLength = partLength;
580             longestPart = part;
581           }
582         }
583       }
584 
585       if ( !longestPart )
586       {
587         // something is really wrong!
588         GEOSGeom_destroy_r( geosctxt, newGeos );
589         return;
590       }
591 
592       geos::unique_ptr longestPartClone( GEOSGeom_clone_r( geosctxt, longestPart ) );
593       GEOSGeom_destroy_r( geosctxt, newGeos );
594       newGeos = longestPartClone.release();
595     }
596 
597     const int newNbPoints = GEOSGeomGetNumPoints_r( geosctxt, newGeos );
598     const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, newGeos );
599     std::vector< double > newX;
600     std::vector< double > newY;
601     newX.resize( newNbPoints );
602     newY.resize( newNbPoints );
603     for ( int i = 0; i < newNbPoints; i++ )
604     {
605       GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &newX[i] );
606       GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &newY[i] );
607     }
608     nbPoints = newNbPoints;
609     x = newX;
610     y = newY;
611   }
612   catch ( GEOSException &e )
613   {
614     qWarning( "GEOS exception: %s", e.what() );
615     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
616     return;
617   }
618 
619   invalidateGeos();
620   mGeos = newGeos;
621   mOwnsGeom = true;
622 }
623 
extendLineByDistance(double startDistance,double endDistance,double smoothDistance)624 void PointSet::extendLineByDistance( double startDistance, double endDistance, double smoothDistance )
625 {
626   if ( nbPoints < 2 )
627     return;
628 
629   double x0 = x[0];
630   double y0 = y[0];
631   if ( startDistance > 0 )
632   {
633     // trace forward by smoothDistance
634     double x1 = x[1];
635     double y1 = y[1];
636 
637     double distanceConsumed = 0;
638     double lastX = x0;
639     double lastY = y0;
640     for ( int i = 1; i < nbPoints; ++i )
641     {
642       const double thisX = x[i];
643       const double thisY = y[i];
644       const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
645       distanceConsumed += thisSegmentLength;
646       if ( distanceConsumed >= smoothDistance )
647       {
648         const double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
649         x1 = lastX + c * ( thisX - lastX );
650         y1 = lastY + c * ( thisY - lastY );
651         break;
652       }
653       lastX = thisX;
654       lastY = thisY;
655     }
656 
657     const double distance = std::sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
658     const double extensionFactor = ( startDistance + distance ) / distance;
659     const QgsPointXY newStart = QgsGeometryUtils::interpolatePointOnLine( x1, y1, x0, y0, extensionFactor );
660     x0 = newStart.x();
661     y0 = newStart.y();
662     // defer actually changing the stored start until we've calculated the new end point
663   }
664 
665   if ( endDistance > 0 )
666   {
667     const double xend0 = x[nbPoints - 1];
668     const double yend0 = y[nbPoints - 1];
669     double xend1 = x[nbPoints - 2];
670     double yend1 = y[nbPoints - 2];
671 
672     // trace backward by smoothDistance
673     double distanceConsumed = 0;
674     double lastX = x0;
675     double lastY = y0;
676     for ( int i = nbPoints - 2; i >= 0; --i )
677     {
678       const double thisX = x[i];
679       const double thisY = y[i];
680       const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
681       distanceConsumed += thisSegmentLength;
682       if ( distanceConsumed >= smoothDistance )
683       {
684         const double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
685         xend1 = lastX + c * ( thisX - lastX );
686         yend1 = lastY + c * ( thisY - lastY );
687         break;
688       }
689       lastX = thisX;
690       lastY = thisY;
691     }
692 
693     const double distance = std::sqrt( ( xend1 - xend0 ) * ( xend1 - xend0 ) + ( yend1 - yend0 ) * ( yend1 - yend0 ) );
694     const double extensionFactor = ( endDistance + distance ) / distance;
695     const QgsPointXY newEnd = QgsGeometryUtils::interpolatePointOnLine( xend1, yend1, xend0, yend0, extensionFactor );
696     x.emplace_back( newEnd.x() );
697     y.emplace_back( newEnd.y() );
698     nbPoints++;
699   }
700 
701   if ( startDistance > 0 )
702   {
703     x.insert( x.begin(), x0 );
704     y.insert( y.begin(), y0 );
705     nbPoints++;
706   }
707 
708   invalidateGeos();
709 }
710 
computeConvexHullOrientedBoundingBox(bool & ok)711 OrientedConvexHullBoundingBox PointSet::computeConvexHullOrientedBoundingBox( bool &ok )
712 {
713   ok = false;
714   double bbox[4]; // xmin, ymin, xmax, ymax
715 
716   double alpha;
717   int alpha_d;
718 
719   double alpha_seg;
720 
721   double d1, d2;
722 
723   double bb[16];   // {ax, ay, bx, by, cx, cy, dx, dy, ex, ey, fx, fy, gx, gy, hx, hy}}
724 
725   double best_area = std::numeric_limits<double>::max();
726   double best_alpha = -1;
727   double best_bb[16];
728   double best_length = 0;
729   double best_width = 0;
730 
731 
732   bbox[0] = std::numeric_limits<double>::max();
733   bbox[1] = std::numeric_limits<double>::max();
734   bbox[2] = std::numeric_limits<double>::lowest();
735   bbox[3] = std::numeric_limits<double>::lowest();
736 
737   for ( std::size_t i = 0; i < convexHull.size(); i++ )
738   {
739     if ( x[convexHull[i]] < bbox[0] )
740       bbox[0] = x[convexHull[i]];
741 
742     if ( x[convexHull[i]] > bbox[2] )
743       bbox[2] = x[convexHull[i]];
744 
745     if ( y[convexHull[i]] < bbox[1] )
746       bbox[1] = y[convexHull[i]];
747 
748     if ( y[convexHull[i]] > bbox[3] )
749       bbox[3] = y[convexHull[i]];
750   }
751 
752   OrientedConvexHullBoundingBox finalBb;
753 
754   const double dref = bbox[2] - bbox[0];
755   if ( qgsDoubleNear( dref, 0 ) )
756   {
757     ok = false;
758     return finalBb;
759   }
760 
761   for ( alpha_d = 0; alpha_d < 90; alpha_d++ )
762   {
763     alpha = alpha_d *  M_PI / 180.0;
764     d1 = std::cos( alpha ) * dref;
765     d2 = std::sin( alpha ) * dref;
766 
767     bb[0]  = bbox[0];
768     bb[1]  = bbox[3]; // ax, ay
769 
770     bb[4]  = bbox[0];
771     bb[5]  = bbox[1]; // cx, cy
772 
773     bb[8]  = bbox[2];
774     bb[9]  = bbox[1]; // ex, ey
775 
776     bb[12] = bbox[2];
777     bb[13] = bbox[3]; // gx, gy
778 
779 
780     bb[2]  = bb[0] + d1;
781     bb[3]  = bb[1] + d2; // bx, by
782     bb[6]  = bb[4] - d2;
783     bb[7]  = bb[5] + d1; // dx, dy
784     bb[10] = bb[8] - d1;
785     bb[11] = bb[9] - d2; // fx, fy
786     bb[14] = bb[12] + d2;
787     bb[15] = bb[13] - d1; // hx, hy
788 
789     // adjust all points
790     for ( int  i = 0; i < 16; i += 4 )
791     {
792 
793       alpha_seg = ( ( i / 4 > 0 ? ( i / 4 ) - 1 : 3 ) ) * M_PI_2 + alpha;
794 
795       double best_cp = std::numeric_limits<double>::max();
796 
797       for ( std::size_t j = 0; j < convexHull.size(); j++ )
798       {
799         const double cp = GeomFunction::cross_product( bb[i + 2], bb[i + 3], bb[i], bb[i + 1], x[convexHull[j]], y[convexHull[j]] );
800         if ( cp < best_cp )
801         {
802           best_cp = cp;
803         }
804       }
805 
806       const double distNearestPoint = best_cp / dref;
807 
808       d1 = std::cos( alpha_seg ) * distNearestPoint;
809       d2 = std::sin( alpha_seg ) * distNearestPoint;
810 
811       bb[i]   += d1; // x
812       bb[i + 1] += d2; // y
813       bb[i + 2] += d1; // x
814       bb[i + 3] += d2; // y
815     }
816 
817     // compute and compare AREA
818     const double width = GeomFunction::cross_product( bb[6], bb[7], bb[4], bb[5], bb[12], bb[13] ) / dref;
819     const double length = GeomFunction::cross_product( bb[2], bb[3], bb[0], bb[1], bb[8], bb[9] ) / dref;
820 
821     double area = width * length;
822 
823     if ( area < 0 )
824       area *= -1;
825 
826 
827     if ( best_area - area > EPSILON )
828     {
829       best_area = area;
830       best_length = length;
831       best_width = width;
832       best_alpha = alpha;
833       memcpy( best_bb, bb, sizeof( double ) * 16 );
834     }
835   }
836 
837   // best bbox is defined
838   for ( int i = 0; i < 16; i = i + 4 )
839   {
840     GeomFunction::computeLineIntersection( best_bb[i], best_bb[i + 1], best_bb[i + 2], best_bb[i + 3],
841                                            best_bb[( i + 4 ) % 16], best_bb[( i + 5 ) % 16], best_bb[( i + 6 ) % 16], best_bb[( i + 7 ) % 16],
842                                            &finalBb.x[int ( i / 4 )], &finalBb.y[int ( i / 4 )] );
843   }
844 
845   finalBb.alpha = best_alpha;
846   finalBb.width = best_width;
847   finalBb.length = best_length;
848 
849   ok = true;
850   return finalBb;
851 }
852 
minDistanceToPoint(double px,double py,double * rx,double * ry) const853 double PointSet::minDistanceToPoint( double px, double py, double *rx, double *ry ) const
854 {
855   if ( !mGeos )
856     createGeosGeom();
857 
858   if ( !mGeos )
859     return 0;
860 
861   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
862   try
863   {
864 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
865     geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, px, py ) );
866 #else
867     GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
868     GEOSCoordSeq_setX_r( geosctxt, coord, 0, px );
869     GEOSCoordSeq_setY_r( geosctxt, coord, 0, py );
870     geos::unique_ptr geosPt( GEOSGeom_createPoint_r( geosctxt, coord ) );
871 #endif
872     const int type = GEOSGeomTypeId_r( geosctxt, mGeos );
873     const GEOSGeometry *extRing = nullptr;
874 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=9
875     const GEOSPreparedGeometry *preparedExtRing = nullptr;
876 #endif
877 
878     if ( type != GEOS_POLYGON )
879     {
880       extRing = mGeos;
881 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=9
882       preparedExtRing = preparedGeom();
883 #endif
884     }
885     else
886     {
887       //for polygons, we want distance to exterior ring (not an interior point)
888       extRing = GEOSGetExteriorRing_r( geosctxt, mGeos );
889 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
890       if ( ! mGeosPreparedBoundary )
891       {
892         mGeosPreparedBoundary = GEOSPrepare_r( geosctxt, extRing );
893       }
894       preparedExtRing = mGeosPreparedBoundary;
895 #endif
896     }
897 
898 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
899     const geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, preparedExtRing, geosPt.get() ) );
900 #else
901     geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosctxt, extRing, geosPt.get() ) );
902 #endif
903     double nx;
904     double ny;
905 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
906     unsigned int nPoints = 0;
907     GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
908     if ( nPoints == 0 )
909       return 0;
910 
911     ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
912 #else
913     ( void )GEOSCoordSeq_getX_r( geosctxt, nearestCoord.get(), 0, &nx );
914     ( void )GEOSCoordSeq_getY_r( geosctxt, nearestCoord.get(), 0, &ny );
915 #endif
916 
917     if ( rx )
918       *rx = nx;
919     if ( ry )
920       *ry = ny;
921 
922     return GeomFunction::dist_euc2d_sq( px, py, nx, ny );
923   }
924   catch ( GEOSException &e )
925   {
926     qWarning( "GEOS exception: %s", e.what() );
927     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
928     return 0;
929   }
930 }
931 
getCentroid(double & px,double & py,bool forceInside) const932 void PointSet::getCentroid( double &px, double &py, bool forceInside ) const
933 {
934   if ( !mGeos )
935     createGeosGeom();
936 
937   if ( !mGeos )
938     return;
939 
940   try
941   {
942     GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
943     geos::unique_ptr centroidGeom( GEOSGetCentroid_r( geosctxt, mGeos ) );
944     if ( centroidGeom )
945     {
946       const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, centroidGeom.get() );
947 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
948       unsigned int nPoints = 0;
949       GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
950       if ( nPoints == 0 )
951         return;
952       GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
953 #else
954       GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
955       GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
956 #endif
957     }
958 
959     // check if centroid inside in polygon
960     if ( forceInside && !containsPoint( px, py ) )
961     {
962       geos::unique_ptr pointGeom( GEOSPointOnSurface_r( geosctxt, mGeos ) );
963 
964       if ( pointGeom )
965       {
966         const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
967 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
968         unsigned int nPoints = 0;
969         GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
970         if ( nPoints == 0 )
971           return;
972 
973         GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
974 #else
975         GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
976         GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
977 #endif
978       }
979     }
980   }
981   catch ( GEOSException &e )
982   {
983     qWarning( "GEOS exception: %s", e.what() );
984     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
985     return;
986   }
987 }
988 
boundingBoxIntersects(const PointSet * other) const989 bool PointSet::boundingBoxIntersects( const PointSet *other ) const
990 {
991   const double x1 = ( xmin > other->xmin ? xmin : other->xmin );
992   const double x2 = ( xmax < other->xmax ? xmax : other->xmax );
993   if ( x1 > x2 )
994     return false;
995   const double y1 = ( ymin > other->ymin ? ymin : other->ymin );
996   const double y2 = ( ymax < other->ymax ? ymax : other->ymax );
997   return y1 <= y2;
998 }
999 
getPointByDistance(double * d,double * ad,double dl,double * px,double * py)1000 void PointSet::getPointByDistance( double *d, double *ad, double dl, double *px, double *py )
1001 {
1002   int i;
1003   double dx, dy, di;
1004   double distr;
1005 
1006   i = 0;
1007   if ( dl >= 0 )
1008   {
1009     while ( i < nbPoints && ad[i] <= dl ) i++;
1010     i--;
1011   }
1012 
1013   if ( i < nbPoints - 1 )
1014   {
1015     if ( dl < 0 )
1016     {
1017       dx = x[nbPoints - 1] - x[0];
1018       dy = y[nbPoints - 1] - y[0];
1019       di = std::sqrt( dx * dx + dy * dy );
1020     }
1021     else
1022     {
1023       dx = x[i + 1] - x[i];
1024       dy = y[i + 1] - y[i];
1025       di = d[i];
1026     }
1027 
1028     distr = dl - ad[i];
1029     *px = x[i] + dx * distr / di;
1030     *py = y[i] + dy * distr / di;
1031   }
1032   else    // just select last point...
1033   {
1034     *px = x[i];
1035     *py = y[i];
1036   }
1037 }
1038 
geos() const1039 const GEOSGeometry *PointSet::geos() const
1040 {
1041   if ( !mGeos )
1042     createGeosGeom();
1043 
1044   return mGeos;
1045 }
1046 
length() const1047 double PointSet::length() const
1048 {
1049   if ( mLength >= 0 )
1050     return mLength;
1051 
1052   if ( !mGeos )
1053     createGeosGeom();
1054 
1055   if ( !mGeos )
1056     return -1;
1057 
1058   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1059 
1060   try
1061   {
1062     ( void )GEOSLength_r( geosctxt, mGeos, &mLength );
1063     return mLength;
1064   }
1065   catch ( GEOSException &e )
1066   {
1067     qWarning( "GEOS exception: %s", e.what() );
1068     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1069     return -1;
1070   }
1071 }
1072 
area() const1073 double PointSet::area() const
1074 {
1075   if ( mArea >= 0 )
1076     return mArea;
1077 
1078   if ( !mGeos )
1079     createGeosGeom();
1080 
1081   if ( !mGeos )
1082     return -1;
1083 
1084   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1085 
1086   try
1087   {
1088     ( void )GEOSArea_r( geosctxt, mGeos, &mArea );
1089     mArea = std::fabs( mArea );
1090     return mArea;
1091   }
1092   catch ( GEOSException &e )
1093   {
1094     qWarning( "GEOS exception: %s", e.what() );
1095     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1096     return -1;
1097   }
1098 }
1099 
isClosed() const1100 bool PointSet::isClosed() const
1101 {
1102   return qgsDoubleNear( x[0], x[nbPoints - 1] ) && qgsDoubleNear( y[0], y[nbPoints - 1] );
1103 }
1104 
toWkt() const1105 QString PointSet::toWkt() const
1106 {
1107   if ( !mGeos )
1108     createGeosGeom();
1109 
1110   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1111 
1112   try
1113   {
1114     GEOSWKTWriter *writer = GEOSWKTWriter_create_r( geosctxt );
1115 
1116     char *wkt = GEOSWKTWriter_write_r( geosctxt, writer, mGeos );
1117     const QString res( wkt );
1118 
1119     GEOSFree_r( geosctxt, wkt );
1120 
1121     GEOSWKTWriter_destroy_r( geosctxt, writer );
1122     writer = nullptr;
1123 
1124     return res;
1125   }
1126   catch ( GEOSException &e )
1127   {
1128     qWarning( "GEOS exception: %s", e.what() );
1129     QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1130     return QString();
1131   }
1132 }
1133 
edgeDistances() const1134 std::tuple< std::vector< double >, double > PointSet::edgeDistances() const
1135 {
1136   std::vector< double > distances( nbPoints );
1137   double totalDistance = 0;
1138   double oldX = -1.0, oldY = -1.0;
1139   for ( int i = 0; i < nbPoints; i++ )
1140   {
1141     if ( i == 0 )
1142       distances[i] = 0;
1143     else
1144       distances[i] = std::sqrt( std::pow( oldX - x[i], 2 ) + std::pow( oldY - y[i], 2 ) );
1145 
1146     oldX = x[i];
1147     oldY = y[i];
1148     totalDistance += distances[i];
1149   }
1150   return std::make_tuple( std::move( distances ), totalDistance );
1151 }
1152