QGIS API Documentation  3.20.0-Odense (decaadbb31)
pointset.cpp
Go to the documentation of this file.
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 
42 {
43  nbPoints = 0;
44  type = -1;
45 }
46 
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 
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 
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 
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 
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 
150 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 
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 
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 
229 {
230  x.clear();
231  y.clear();
232 }
233 
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 
261 std::unique_ptr<PointSet> PointSet::clone() const
262 {
263  return std::unique_ptr< PointSet>( new PointSet( *this ) );
264 }
265 
266 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  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 
292 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 
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  std::size_t ihn = ( ihs + 1 ) % shape->convexHull.size();
340 
341  int ips = shape->convexHull[ihs];
342  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  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  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 
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 
562  // happens sometime, if the offset curve self-intersects
563  if ( GEOSGeomTypeId_r( geosctxt, newGeos ) == GEOS_MULTILINESTRING )
564  {
565  // we keep the longest part
566  const int nParts = GEOSGetNumGeometries_r( geosctxt, newGeos );
567  double maximumLength = -1;
568  const GEOSGeometry *longestPart = nullptr;
569  for ( int i = 0; i < nParts; ++i )
570  {
571  const GEOSGeometry *part = GEOSGetGeometryN_r( geosctxt, newGeos, i );
572  double partLength = -1;
573  if ( GEOSLength_r( geosctxt, part, &partLength ) == 1 )
574  {
575  if ( partLength > maximumLength )
576  {
577  maximumLength = partLength;
578  longestPart = part;
579  }
580  }
581  }
582 
583  if ( !longestPart )
584  {
585  // something is really wrong!
586  GEOSGeom_destroy_r( geosctxt, newGeos );
587  return;
588  }
589 
590  geos::unique_ptr longestPartClone( GEOSGeom_clone_r( geosctxt, longestPart ) );
591  GEOSGeom_destroy_r( geosctxt, newGeos );
592  newGeos = longestPartClone.release();
593  }
594 
595  const int newNbPoints = GEOSGeomGetNumPoints_r( geosctxt, newGeos );
596  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, newGeos );
597  std::vector< double > newX;
598  std::vector< double > newY;
599  newX.resize( newNbPoints );
600  newY.resize( newNbPoints );
601  for ( int i = 0; i < newNbPoints; i++ )
602  {
603  GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &newX[i] );
604  GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &newY[i] );
605  }
606  nbPoints = newNbPoints;
607  x = newX;
608  y = newY;
609  }
610  catch ( GEOSException &e )
611  {
612  qWarning( "GEOS exception: %s", e.what() );
613  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
614  return;
615  }
616 
617  invalidateGeos();
618  mGeos = newGeos;
619  mOwnsGeom = true;
620 }
621 
622 void PointSet::extendLineByDistance( double startDistance, double endDistance, double smoothDistance )
623 {
624  if ( nbPoints < 2 )
625  return;
626 
627  double x0 = x[0];
628  double y0 = y[0];
629  if ( startDistance > 0 )
630  {
631  // trace forward by smoothDistance
632  double x1 = x[1];
633  double y1 = y[1];
634 
635  double distanceConsumed = 0;
636  double lastX = x0;
637  double lastY = y0;
638  for ( int i = 1; i < nbPoints; ++i )
639  {
640  double thisX = x[i];
641  double thisY = y[i];
642  const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
643  distanceConsumed += thisSegmentLength;
644  if ( distanceConsumed >= smoothDistance )
645  {
646  double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
647  x1 = lastX + c * ( thisX - lastX );
648  y1 = lastY + c * ( thisY - lastY );
649  break;
650  }
651  lastX = thisX;
652  lastY = thisY;
653  }
654 
655  const double distance = std::sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
656  const double extensionFactor = ( startDistance + distance ) / distance;
657  const QgsPointXY newStart = QgsGeometryUtils::interpolatePointOnLine( x1, y1, x0, y0, extensionFactor );
658  x0 = newStart.x();
659  y0 = newStart.y();
660  // defer actually changing the stored start until we've calculated the new end point
661  }
662 
663  if ( endDistance > 0 )
664  {
665  double xend0 = x[nbPoints - 1];
666  double yend0 = y[nbPoints - 1];
667  double xend1 = x[nbPoints - 2];
668  double yend1 = y[nbPoints - 2];
669 
670  // trace backward by smoothDistance
671  double distanceConsumed = 0;
672  double lastX = x0;
673  double lastY = y0;
674  for ( int i = nbPoints - 2; i >= 0; --i )
675  {
676  double thisX = x[i];
677  double thisY = y[i];
678  const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
679  distanceConsumed += thisSegmentLength;
680  if ( distanceConsumed >= smoothDistance )
681  {
682  double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
683  xend1 = lastX + c * ( thisX - lastX );
684  yend1 = lastY + c * ( thisY - lastY );
685  break;
686  }
687  lastX = thisX;
688  lastY = thisY;
689  }
690 
691  const double distance = std::sqrt( ( xend1 - xend0 ) * ( xend1 - xend0 ) + ( yend1 - yend0 ) * ( yend1 - yend0 ) );
692  const double extensionFactor = ( endDistance + distance ) / distance;
693  const QgsPointXY newEnd = QgsGeometryUtils::interpolatePointOnLine( xend1, yend1, xend0, yend0, extensionFactor );
694  x.emplace_back( newEnd.x() );
695  y.emplace_back( newEnd.y() );
696  nbPoints++;
697  }
698 
699  if ( startDistance > 0 )
700  {
701  x.insert( x.begin(), x0 );
702  y.insert( y.begin(), y0 );
703  nbPoints++;
704  }
705 
706  invalidateGeos();
707 }
708 
710 {
711  ok = false;
712  double bbox[4]; // xmin, ymin, xmax, ymax
713 
714  double alpha;
715  int alpha_d;
716 
717  double alpha_seg;
718 
719  double d1, d2;
720 
721  double bb[16]; // {ax, ay, bx, by, cx, cy, dx, dy, ex, ey, fx, fy, gx, gy, hx, hy}}
722 
723  double best_area = std::numeric_limits<double>::max();
724  double best_alpha = -1;
725  double best_bb[16];
726  double best_length = 0;
727  double best_width = 0;
728 
729 
730  bbox[0] = std::numeric_limits<double>::max();
731  bbox[1] = std::numeric_limits<double>::max();
732  bbox[2] = std::numeric_limits<double>::lowest();
733  bbox[3] = std::numeric_limits<double>::lowest();
734 
735  for ( std::size_t i = 0; i < convexHull.size(); i++ )
736  {
737  if ( x[convexHull[i]] < bbox[0] )
738  bbox[0] = x[convexHull[i]];
739 
740  if ( x[convexHull[i]] > bbox[2] )
741  bbox[2] = x[convexHull[i]];
742 
743  if ( y[convexHull[i]] < bbox[1] )
744  bbox[1] = y[convexHull[i]];
745 
746  if ( y[convexHull[i]] > bbox[3] )
747  bbox[3] = y[convexHull[i]];
748  }
749 
751 
752  const double dref = bbox[2] - bbox[0];
753  if ( qgsDoubleNear( dref, 0 ) )
754  {
755  ok = false;
756  return finalBb;
757  }
758 
759  for ( alpha_d = 0; alpha_d < 90; alpha_d++ )
760  {
761  alpha = alpha_d * M_PI / 180.0;
762  d1 = std::cos( alpha ) * dref;
763  d2 = std::sin( alpha ) * dref;
764 
765  bb[0] = bbox[0];
766  bb[1] = bbox[3]; // ax, ay
767 
768  bb[4] = bbox[0];
769  bb[5] = bbox[1]; // cx, cy
770 
771  bb[8] = bbox[2];
772  bb[9] = bbox[1]; // ex, ey
773 
774  bb[12] = bbox[2];
775  bb[13] = bbox[3]; // gx, gy
776 
777 
778  bb[2] = bb[0] + d1;
779  bb[3] = bb[1] + d2; // bx, by
780  bb[6] = bb[4] - d2;
781  bb[7] = bb[5] + d1; // dx, dy
782  bb[10] = bb[8] - d1;
783  bb[11] = bb[9] - d2; // fx, fy
784  bb[14] = bb[12] + d2;
785  bb[15] = bb[13] - d1; // hx, hy
786 
787  // adjust all points
788  for ( int i = 0; i < 16; i += 4 )
789  {
790 
791  alpha_seg = ( ( i / 4 > 0 ? ( i / 4 ) - 1 : 3 ) ) * M_PI_2 + alpha;
792 
793  double best_cp = std::numeric_limits<double>::max();
794 
795  for ( std::size_t j = 0; j < convexHull.size(); j++ )
796  {
797  const double cp = GeomFunction::cross_product( bb[i + 2], bb[i + 3], bb[i], bb[i + 1], x[convexHull[j]], y[convexHull[j]] );
798  if ( cp < best_cp )
799  {
800  best_cp = cp;
801  }
802  }
803 
804  const double distNearestPoint = best_cp / dref;
805 
806  d1 = std::cos( alpha_seg ) * distNearestPoint;
807  d2 = std::sin( alpha_seg ) * distNearestPoint;
808 
809  bb[i] += d1; // x
810  bb[i + 1] += d2; // y
811  bb[i + 2] += d1; // x
812  bb[i + 3] += d2; // y
813  }
814 
815  // compute and compare AREA
816  const double width = GeomFunction::cross_product( bb[6], bb[7], bb[4], bb[5], bb[12], bb[13] ) / dref;
817  const double length = GeomFunction::cross_product( bb[2], bb[3], bb[0], bb[1], bb[8], bb[9] ) / dref;
818 
819  double area = width * length;
820 
821  if ( area < 0 )
822  area *= -1;
823 
824 
825  if ( best_area - area > EPSILON )
826  {
827  best_area = area;
828  best_length = length;
829  best_width = width;
830  best_alpha = alpha;
831  memcpy( best_bb, bb, sizeof( double ) * 16 );
832  }
833  }
834 
835  // best bbox is defined
836  for ( int i = 0; i < 16; i = i + 4 )
837  {
838  GeomFunction::computeLineIntersection( best_bb[i], best_bb[i + 1], best_bb[i + 2], best_bb[i + 3],
839  best_bb[( i + 4 ) % 16], best_bb[( i + 5 ) % 16], best_bb[( i + 6 ) % 16], best_bb[( i + 7 ) % 16],
840  &finalBb.x[int ( i / 4 )], &finalBb.y[int ( i / 4 )] );
841  }
842 
843  finalBb.alpha = best_alpha;
844  finalBb.width = best_width;
845  finalBb.length = best_length;
846 
847  ok = true;
848  return finalBb;
849 }
850 
851 double PointSet::minDistanceToPoint( double px, double py, double *rx, double *ry ) const
852 {
853  if ( !mGeos )
854  createGeosGeom();
855 
856  if ( !mGeos )
857  return 0;
858 
859  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
860  try
861  {
862 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
863  geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, px, py ) );
864 #else
865  GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
866  GEOSCoordSeq_setX_r( geosctxt, coord, 0, px );
867  GEOSCoordSeq_setY_r( geosctxt, coord, 0, py );
868  geos::unique_ptr geosPt( GEOSGeom_createPoint_r( geosctxt, coord ) );
869 #endif
870  int type = GEOSGeomTypeId_r( geosctxt, mGeos );
871  const GEOSGeometry *extRing = nullptr;
872 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=9
873  const GEOSPreparedGeometry *preparedExtRing = nullptr;
874 #endif
875 
876  if ( type != GEOS_POLYGON )
877  {
878  extRing = mGeos;
879 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=9
880  preparedExtRing = preparedGeom();
881 #endif
882  }
883  else
884  {
885  //for polygons, we want distance to exterior ring (not an interior point)
886  extRing = GEOSGetExteriorRing_r( geosctxt, mGeos );
887 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
888  if ( ! mGeosPreparedBoundary )
889  {
890  mGeosPreparedBoundary = GEOSPrepare_r( geosctxt, extRing );
891  }
892  preparedExtRing = mGeosPreparedBoundary;
893 #endif
894  }
895 
896 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
897  geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, preparedExtRing, geosPt.get() ) );
898 #else
899  geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosctxt, extRing, geosPt.get() ) );
900 #endif
901  double nx;
902  double ny;
903 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
904  unsigned int nPoints = 0;
905  GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
906  if ( nPoints == 0 )
907  return 0;
908 
909  ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
910 #else
911  ( void )GEOSCoordSeq_getX_r( geosctxt, nearestCoord.get(), 0, &nx );
912  ( void )GEOSCoordSeq_getY_r( geosctxt, nearestCoord.get(), 0, &ny );
913 #endif
914 
915  if ( rx )
916  *rx = nx;
917  if ( ry )
918  *ry = ny;
919 
920  return GeomFunction::dist_euc2d_sq( px, py, nx, ny );
921  }
922  catch ( GEOSException &e )
923  {
924  qWarning( "GEOS exception: %s", e.what() );
925  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
926  return 0;
927  }
928 }
929 
930 void PointSet::getCentroid( double &px, double &py, bool forceInside ) const
931 {
932  if ( !mGeos )
933  createGeosGeom();
934 
935  if ( !mGeos )
936  return;
937 
938  try
939  {
940  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
941  geos::unique_ptr centroidGeom( GEOSGetCentroid_r( geosctxt, mGeos ) );
942  if ( centroidGeom )
943  {
944  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, centroidGeom.get() );
945 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
946  unsigned int nPoints = 0;
947  GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
948  if ( nPoints == 0 )
949  return;
950  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
951 #else
952  GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
953  GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
954 #endif
955  }
956 
957  // check if centroid inside in polygon
958  if ( forceInside && !containsPoint( px, py ) )
959  {
960  geos::unique_ptr pointGeom( GEOSPointOnSurface_r( geosctxt, mGeos ) );
961 
962  if ( pointGeom )
963  {
964  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
965 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
966  unsigned int nPoints = 0;
967  GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
968  if ( nPoints == 0 )
969  return;
970 
971  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
972 #else
973  GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
974  GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
975 #endif
976  }
977  }
978  }
979  catch ( GEOSException &e )
980  {
981  qWarning( "GEOS exception: %s", e.what() );
982  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
983  return;
984  }
985 }
986 
987 bool PointSet::boundingBoxIntersects( const PointSet *other ) const
988 {
989  double x1 = ( xmin > other->xmin ? xmin : other->xmin );
990  double x2 = ( xmax < other->xmax ? xmax : other->xmax );
991  if ( x1 > x2 )
992  return false;
993  double y1 = ( ymin > other->ymin ? ymin : other->ymin );
994  double y2 = ( ymax < other->ymax ? ymax : other->ymax );
995  return y1 <= y2;
996 }
997 
998 void PointSet::getPointByDistance( double *d, double *ad, double dl, double *px, double *py )
999 {
1000  int i;
1001  double dx, dy, di;
1002  double distr;
1003 
1004  i = 0;
1005  if ( dl >= 0 )
1006  {
1007  while ( i < nbPoints && ad[i] <= dl ) i++;
1008  i--;
1009  }
1010 
1011  if ( i < nbPoints - 1 )
1012  {
1013  if ( dl < 0 )
1014  {
1015  dx = x[nbPoints - 1] - x[0];
1016  dy = y[nbPoints - 1] - y[0];
1017  di = std::sqrt( dx * dx + dy * dy );
1018  }
1019  else
1020  {
1021  dx = x[i + 1] - x[i];
1022  dy = y[i + 1] - y[i];
1023  di = d[i];
1024  }
1025 
1026  distr = dl - ad[i];
1027  *px = x[i] + dx * distr / di;
1028  *py = y[i] + dy * distr / di;
1029  }
1030  else // just select last point...
1031  {
1032  *px = x[i];
1033  *py = y[i];
1034  }
1035 }
1036 
1037 const GEOSGeometry *PointSet::geos() const
1038 {
1039  if ( !mGeos )
1040  createGeosGeom();
1041 
1042  return mGeos;
1043 }
1044 
1045 double PointSet::length() const
1046 {
1047  if ( mLength >= 0 )
1048  return mLength;
1049 
1050  if ( !mGeos )
1051  createGeosGeom();
1052 
1053  if ( !mGeos )
1054  return -1;
1055 
1056  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1057 
1058  try
1059  {
1060  ( void )GEOSLength_r( geosctxt, mGeos, &mLength );
1061  return mLength;
1062  }
1063  catch ( GEOSException &e )
1064  {
1065  qWarning( "GEOS exception: %s", e.what() );
1066  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1067  return -1;
1068  }
1069 }
1070 
1071 double PointSet::area() const
1072 {
1073  if ( mArea >= 0 )
1074  return mArea;
1075 
1076  if ( !mGeos )
1077  createGeosGeom();
1078 
1079  if ( !mGeos )
1080  return -1;
1081 
1082  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1083 
1084  try
1085  {
1086  ( void )GEOSArea_r( geosctxt, mGeos, &mArea );
1087  mArea = std::fabs( mArea );
1088  return mArea;
1089  }
1090  catch ( GEOSException &e )
1091  {
1092  qWarning( "GEOS exception: %s", e.what() );
1093  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1094  return -1;
1095  }
1096 }
1097 
1099 {
1100  return qgsDoubleNear( x[0], x[nbPoints - 1] ) && qgsDoubleNear( y[0], y[nbPoints - 1] );
1101 }
1102 
1103 QString PointSet::toWkt() const
1104 {
1105  if ( !mGeos )
1106  createGeosGeom();
1107 
1108  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
1109 
1110  try
1111  {
1112  GEOSWKTWriter *writer = GEOSWKTWriter_create_r( geosctxt );
1113 
1114  char *wkt = GEOSWKTWriter_write_r( geosctxt, writer, mGeos );
1115  const QString res( wkt );
1116 
1117  GEOSFree_r( geosctxt, wkt );
1118 
1119  GEOSWKTWriter_destroy_r( geosctxt, writer );
1120  writer = nullptr;
1121 
1122  return res;
1123  }
1124  catch ( GEOSException &e )
1125  {
1126  qWarning( "GEOS exception: %s", e.what() );
1127  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1128  return QString();
1129  }
1130 }
1131 
1132 std::tuple< std::vector< double >, double > PointSet::edgeDistances() const
1133 {
1134  std::vector< double > distances( nbPoints );
1135  double totalDistance = 0;
1136  double oldX = -1.0, oldY = -1.0;
1137  for ( int i = 0; i < nbPoints; i++ )
1138  {
1139  if ( i == 0 )
1140  distances[i] = 0;
1141  else
1142  distances[i] = std::sqrt( std::pow( oldX - x[i], 2 ) + std::pow( oldY - y[i], 2 ) );
1143 
1144  oldX = x[i];
1145  oldY = y[i];
1146  totalDistance += distances[i];
1147  }
1148  return std::make_tuple( std::move( distances ), totalDistance );
1149 }
static QgsPointXY interpolatePointOnLine(double x1, double y1, double x2, double y2, double fraction) SIP_HOLDGIL
Interpolates the position of a point a fraction of the way along the line from (x1,...
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:3166
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:67
static std::vector< int > convexHullId(std::vector< int > &id, const std::vector< double > &x, const std::vector< double > &y)
Compute the convex hull in O(n·log(n))
static bool computeLineIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double *x, double *y)
Compute the point where two lines intersect.
static bool isSegIntersects(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
Returns true if the two segments intersect.
static double cross_product(double x1, double y1, double x2, double y2, double x3, double y3)
Definition: geomfunction.h:62
static double dist_euc2d_sq(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:72
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
The underlying raw pal geometry class.
Definition: pointset.h:76
std::unique_ptr< PointSet > clone() const
Returns a copy of the point set.
Definition: pointset.cpp:261
bool containsLabelCandidate(double x, double y, double width, double height, double alpha=0) const
Tests whether a possible label candidate will fit completely within the shape.
Definition: pointset.cpp:292
OrientedConvexHullBoundingBox computeConvexHullOrientedBoundingBox(bool &ok)
Computes an oriented bounding box for the shape's convex hull.
Definition: pointset.cpp:709
double length() const
Returns length of line geometry.
Definition: pointset.cpp:1045
void deleteCoords()
Definition: pointset.cpp:228
void extendLineByDistance(double startDistance, double endDistance, double smoothDistance)
Extends linestrings by the specified amount at the start and end of the line, by extending the existi...
Definition: pointset.cpp:622
virtual ~PointSet()
Definition: pointset.cpp:197
double ymax
Definition: pointset.h:250
double ymin
Definition: pointset.h:249
double area() const
Returns area of polygon geometry.
Definition: pointset.cpp:1071
bool isClosed() const
Returns true if pointset is closed.
Definition: pointset.cpp:1098
PointSet * holeOf
Definition: pointset.h:230
void createGeosGeom() const
Definition: pointset.cpp:100
std::vector< double > y
Definition: pointset.h:220
bool boundingBoxIntersects(const PointSet *other) const
Returns true if the bounding box of this pointset intersects the bounding box of another pointset.
Definition: pointset.cpp:987
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:930
std::vector< double > x
Definition: pointset.h:219
void offsetCurveByDistance(double distance)
Offsets linestrings by the specified distance.
Definition: pointset.cpp:548
std::vector< int > convexHull
Definition: pointset.h:226
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:150
QString toWkt() const
Returns a WKT representation of the point set.
Definition: pointset.cpp:1103
GEOSGeometry * mGeos
Definition: pointset.h:223
double xmin
Definition: pointset.h:247
const GEOSGeometry * geos() const
Returns the point set's GEOS geometry.
Definition: pointset.cpp:1037
void invalidateGeos()
Definition: pointset.cpp:162
double minDistanceToPoint(double px, double py, double *rx=nullptr, double *ry=nullptr) const
Returns the squared minimum distance between the point set geometry and the point (px,...
Definition: pointset.cpp:851
double xmax
Definition: pointset.h:248
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py)
Gets a point a set distance along a line geometry.
Definition: pointset.cpp:998
std::unique_ptr< PointSet > extractShape(int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty)
Does...
Definition: pointset.cpp:234
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:266
std::tuple< std::vector< double >, double > edgeDistances() const
Returns a vector of edge distances as well as its total length.
Definition: pointset.cpp:1132
PointSet * parent
Definition: pointset.h:231
double mLength
Definition: pointset.h:234
double mArea
Definition: pointset.h:233
bool mOwnsGeom
Definition: pointset.h:224
static QLinkedList< PointSet * > splitPolygons(PointSet *inputShape, double labelWidth, double labelHeight)
Split a polygon using some random logic into some other polygons.
Definition: pointset.cpp:297
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition: qgsgeos.h:94
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
Represents the minimum area, oriented bounding box surrounding a convex hull.
Definition: pointset.h:59
#define EPSILON
Definition: util.h:78