QGIS API Documentation  3.12.1-București (121cc00ff0)
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 = cHullSize = 0;
44  cHull = nullptr;
45  type = -1;
46 }
47 
48 PointSet::PointSet( int nbPoints, double *x, double *y )
49  : nbPoints( nbPoints )
50  , type( GEOS_POLYGON )
51 {
52  this->x.resize( nbPoints );
53  this->y.resize( nbPoints );
54  int i;
55 
56  for ( i = 0; i < nbPoints; i++ )
57  {
58  this->x[i] = x[i];
59  this->y[i] = y[i];
60  }
61 
62 }
63 
64 PointSet::PointSet( double aX, double aY )
65  : type( GEOS_POINT )
66  , xmin( aX )
67  , xmax( aY )
68  , ymin( aX )
69  , ymax( aY )
70 
71 {
72  nbPoints = cHullSize = 1;
73  x.resize( 1 );
74  y.resize( 1 );
75  x[0] = aX;
76  y[0] = aY;
77 }
78 
80  : xmin( ps.xmin )
81  , xmax( ps.xmax )
82  , ymin( ps.ymin )
83  , ymax( ps.ymax )
84 {
85  int i;
86 
87  nbPoints = ps.nbPoints;
88  x = ps.x;
89  y = ps.y;
90 
91  if ( ps.cHull )
92  {
93  cHullSize = ps.cHullSize;
94  cHull = new int[cHullSize];
95  for ( i = 0; i < cHullSize; i++ )
96  {
97  cHull[i] = ps.cHull[i];
98  }
99  }
100  else
101  {
102  cHull = nullptr;
103  cHullSize = 0;
104  }
105 
106  type = ps.type;
107 
108  holeOf = ps.holeOf;
109 
110  if ( ps.mGeos )
111  {
112  mGeos = GEOSGeom_clone_r( QgsGeos::getGEOSHandler(), ps.mGeos );
113  mOwnsGeom = true;
114  }
115 }
116 
118 {
119  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
120 
121  bool needClose = false;
122  if ( type == GEOS_POLYGON && ( !qgsDoubleNear( x[0], x[ nbPoints - 1] ) || !qgsDoubleNear( y[0], y[ nbPoints - 1 ] ) ) )
123  {
124  needClose = true;
125  }
126 
127  GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, nbPoints + ( needClose ? 1 : 0 ), 2 );
128  for ( int i = 0; i < nbPoints; ++i )
129  {
130 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
131  GEOSCoordSeq_setXY_r( geosctxt, coord, i, x[i], y[i] );
132 #else
133  GEOSCoordSeq_setX_r( geosctxt, coord, i, x[i] );
134  GEOSCoordSeq_setY_r( geosctxt, coord, i, y[i] );
135 #endif
136  }
137 
138  //close ring if needed
139  if ( needClose )
140  {
141 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
142  GEOSCoordSeq_setXY_r( geosctxt, coord, nbPoints, x[0], y[0] );
143 #else
144  GEOSCoordSeq_setX_r( geosctxt, coord, nbPoints, x[0] );
145  GEOSCoordSeq_setY_r( geosctxt, coord, nbPoints, y[0] );
146 #endif
147  }
148 
149  switch ( type )
150  {
151  case GEOS_POLYGON:
152  mGeos = GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 );
153  break;
154 
155  case GEOS_LINESTRING:
156  mGeos = GEOSGeom_createLineString_r( geosctxt, coord );
157  break;
158 
159  case GEOS_POINT:
160  mGeos = GEOSGeom_createPoint_r( geosctxt, coord );
161  break;
162  }
163 
164  mOwnsGeom = true;
165 }
166 
167 const GEOSPreparedGeometry *PointSet::preparedGeom() const
168 {
169  if ( !mGeos )
170  createGeosGeom();
171 
172  if ( !mPreparedGeom )
173  {
174  mPreparedGeom = GEOSPrepare_r( QgsGeos::getGEOSHandler(), mGeos );
175  }
176  return mPreparedGeom;
177 }
178 
180 {
181  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
182  if ( mOwnsGeom ) // delete old geometry if we own it
183  GEOSGeom_destroy_r( geosctxt, mGeos );
184  GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
185  mOwnsGeom = false;
186  mGeos = nullptr;
187  mPreparedGeom = nullptr;
188  mLength = -1;
189  mArea = -1;
190 }
191 
193 {
194  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
195 
196  if ( mGeos && mOwnsGeom )
197  {
198  GEOSGeom_destroy_r( geosctxt, mGeos );
199  mGeos = nullptr;
200  }
201  GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
202 
203  deleteCoords();
204 
205  delete[] cHull;
206 }
207 
209 {
210  x.clear();
211  y.clear();
212 }
213 
214 std::unique_ptr<PointSet> PointSet::extractShape( int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty )
215 {
216  int i, j;
217 
218  std::unique_ptr<PointSet> newShape = qgis::make_unique< PointSet >();
219  newShape->type = GEOS_POLYGON;
220  newShape->nbPoints = nbPtSh;
221  newShape->x.resize( newShape->nbPoints );
222  newShape->y.resize( newShape->nbPoints );
223 
224  // new shape # 1 from imin to imax
225  for ( j = 0, i = imin; i != ( imax + 1 ) % nbPoints; i = ( i + 1 ) % nbPoints, j++ )
226  {
227  newShape->x[j] = x[i];
228  newShape->y[j] = y[i];
229  }
230  // is the cutting point a new one ?
231  if ( fps != fpe )
232  {
233  // yes => so add it
234  newShape->x[j] = fptx;
235  newShape->y[j] = fpty;
236  }
237 
238  return newShape;
239 }
240 
241 std::unique_ptr<PointSet> PointSet::clone() const
242 {
243  return std::unique_ptr< PointSet>( new PointSet( *this ) );
244 }
245 
246 bool PointSet::containsPoint( double x, double y ) const
247 {
248  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
249  try
250  {
251 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
252  geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, x, y ) );
253 #else
254  GEOSCoordSequence *seq = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
255  GEOSCoordSeq_setX_r( geosctxt, seq, 0, x );
256  GEOSCoordSeq_setY_r( geosctxt, seq, 0, y );
257  geos::unique_ptr point( GEOSGeom_createPoint_r( geosctxt, seq ) );
258 #endif
259  bool result = ( GEOSPreparedContainsProperly_r( geosctxt, preparedGeom(), point.get() ) == 1 );
260 
261  return result;
262  }
263  catch ( GEOSException &e )
264  {
265  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
266  return false;
267  }
268 
269 }
270 
271 bool PointSet::containsLabelCandidate( double x, double y, double width, double height, double alpha ) const
272 {
273  return GeomFunction::containsCandidate( preparedGeom(), x, y, width, height, alpha );
274 }
275 
276 void PointSet::splitPolygons( QLinkedList<PointSet *> &inputShapes,
277  QLinkedList<PointSet *> &outputShapes,
278  double xrm, double yrm )
279 {
280  int i, j;
281 
282  int nbp;
283  std::vector< double > x;
284  std::vector< double > y;
285 
286  int *pts = nullptr;
287 
288  int *cHull = nullptr;
289  int cHullSize;
290 
291  double cp;
292  double bestcp = 0;
293 
294  double bestArea = 0;
295  double area;
296 
297  double base;
298  double b, c;
299  double s;
300 
301  int ihs;
302  int ihn;
303 
304  int ips;
305  int ipn;
306 
307  int holeS = -1; // hole start and end points
308  int holeE = -1;
309 
310  int retainedPt = -1;
311  int pt = 0;
312 
313  double labelArea = xrm * yrm;
314 
315  PointSet *shape = nullptr;
316 
317  while ( !inputShapes.isEmpty() )
318  {
319  shape = inputShapes.takeFirst();
320 
321  x = shape->x;
322  y = shape->y;
323  nbp = shape->nbPoints;
324  pts = new int[nbp];
325 
326  for ( i = 0; i < nbp; i++ )
327  {
328  pts[i] = i;
329  }
330 
331  // conpute convex hull
332  shape->cHullSize = GeomFunction::convexHullId( pts, x, y, nbp, shape->cHull );
333 
334  cHull = shape->cHull;
335  cHullSize = shape->cHullSize;
336 
337  bestArea = 0;
338  retainedPt = -1;
339 
340  // lookup for a hole
341  for ( ihs = 0; ihs < cHullSize; ihs++ )
342  {
343  // ihs->ihn => cHull'seg
344  ihn = ( ihs + 1 ) % cHullSize;
345 
346  ips = cHull[ihs];
347  ipn = ( ips + 1 ) % nbp;
348  if ( ipn != cHull[ihn] ) // next point on shape is not the next point on cHull => there is a hole here !
349  {
350  bestcp = 0;
351  pt = -1;
352  // lookup for the deepest point in the hole
353  for ( i = ips; i != cHull[ihn]; i = ( i + 1 ) % nbp )
354  {
355  cp = std::fabs( GeomFunction::cross_product( x[cHull[ihs]], y[cHull[ihs]],
356  x[cHull[ihn]], y[cHull[ihn]],
357  x[i], y[i] ) );
358  if ( cp - bestcp > EPSILON )
359  {
360  bestcp = cp;
361  pt = i;
362  }
363  }
364 
365  if ( pt != -1 )
366  {
367  // compute the ihs->ihn->pt triangle's area
368  base = GeomFunction::dist_euc2d( x[cHull[ihs]], y[cHull[ihs]],
369  x[cHull[ihn]], y[cHull[ihn]] );
370 
371  b = GeomFunction::dist_euc2d( x[cHull[ihs]], y[cHull[ihs]],
372  x[pt], y[pt] );
373 
374  c = GeomFunction::dist_euc2d( x[cHull[ihn]], y[cHull[ihn]],
375  x[pt], y[pt] );
376 
377  s = ( base + b + c ) / 2; // s = half perimeter
378  area = s * ( s - base ) * ( s - b ) * ( s - c );
379  if ( area < 0 )
380  area = -area;
381 
382  // retain the biggest area
383  if ( area - bestArea > EPSILON )
384  {
385  bestArea = area;
386  retainedPt = pt;
387  holeS = ihs;
388  holeE = ihn;
389  }
390  }
391  }
392  }
393 
394  // we have a hole, its area, and the deppest point in hole
395  // we're going to find the second point to cup the shape
396  // holeS = hole starting point
397  // holeE = hole ending point
398  // retainedPt = deppest point in hole
399  // bestArea = area of triangle HoleS->holeE->retainedPoint
400  bestArea = std::sqrt( bestArea );
401  double cx, cy, dx, dy, ex, ey, fx, fy, seg_length, ptx = 0, pty = 0, fptx = 0, fpty = 0;
402  int ps = -1, pe = -1, fps = -1, fpe = -1;
403  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)
404  {
405  c = std::numeric_limits<double>::max();
406 
407  // iterate on all shape points except points which are in the hole
408  bool isValid;
409  int k, l;
410  for ( i = ( cHull[holeE] + 1 ) % nbp; i != ( cHull[holeS] - 1 + nbp ) % nbp; i = j )
411  {
412  j = ( i + 1 ) % nbp; // i->j is shape segment not in hole
413 
414  // compute distance between retainedPoint and segment
415  // whether perpendicular distance (if retaindPoint is fronting segment i->j)
416  // or distance between retainedPt and i or j (choose the nearest)
417  seg_length = GeomFunction::dist_euc2d( x[i], y[i], x[j], y[j] );
418  cx = ( x[i] + x[j] ) / 2.0;
419  cy = ( y[i] + y[j] ) / 2.0;
420  dx = cy - y[i];
421  dy = cx - x[i];
422 
423  ex = cx - dx;
424  ey = cy + dy;
425  fx = cx + dx;
426  fy = cy - dy;
427 
428  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
429  {
430  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] ) ) )
431  {
432  b = ex;
433  ps = i;
434  pe = i;
435  }
436  else
437  {
438  b = ey;
439  ps = j;
440  pe = j;
441  }
442  }
443  else // point fronting i->j => compute pependicular distance => create a new point
444  {
445  b = GeomFunction::cross_product( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt] ) / seg_length;
446  b *= b;
447  ps = i;
448  pe = j;
449 
450  if ( !GeomFunction::computeLineIntersection( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt], x[retainedPt] - dx, y[retainedPt] + dy, &ptx, &pty ) )
451  {
452  //error - it should intersect the line
453  }
454  }
455 
456  isValid = true;
457  double pointX, pointY;
458  if ( ps == pe )
459  {
460  pointX = x[pe];
461  pointY = y[pe];
462  }
463  else
464  {
465  pointX = ptx;
466  pointY = pty;
467  }
468 
469  for ( k = cHull[holeS]; k != cHull[holeE]; k = ( k + 1 ) % nbp )
470  {
471  l = ( k + 1 ) % nbp;
472  if ( GeomFunction::isSegIntersects( x[retainedPt], y[retainedPt], pointX, pointY, x[k], y[k], x[l], y[l] ) )
473  {
474  isValid = false;
475  break;
476  }
477  }
478 
479 
480  if ( isValid && b < c )
481  {
482  c = b;
483  fps = ps;
484  fpe = pe;
485  fptx = ptx;
486  fpty = pty;
487  }
488  } // for point which are not in hole
489 
490  // we will cut the shapeu in two new shapes, one from [retainedPoint] to [newPoint] and one form [newPoint] to [retainedPoint]
491  int imin = retainedPt;
492  int imax = ( ( ( fps < retainedPt && fpe < retainedPt ) || ( fps > retainedPt && fpe > retainedPt ) ) ? std::min( fps, fpe ) : std::max( fps, fpe ) );
493 
494  int nbPtSh1, nbPtSh2; // how many points in new shapes ?
495  if ( imax > imin )
496  nbPtSh1 = imax - imin + 1 + ( fpe != fps );
497  else
498  nbPtSh1 = imax + nbp - imin + 1 + ( fpe != fps );
499 
500  if ( ( imax == fps ? fpe : fps ) < imin )
501  nbPtSh2 = imin - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
502  else
503  nbPtSh2 = imin + nbp - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
504 
505  if ( retainedPt == -1 || fps == -1 || fpe == -1 )
506  {
507  if ( shape->parent )
508  delete shape;
509  }
510  // check for useless splitting
511  else if ( imax == imin || nbPtSh1 <= 2 || nbPtSh2 <= 2 || nbPtSh1 == nbp || nbPtSh2 == nbp )
512  {
513  outputShapes.append( shape );
514  }
515  else
516  {
517 
518  PointSet *newShape = shape->extractShape( nbPtSh1, imin, imax, fps, fpe, fptx, fpty ).release();
519 
520  if ( shape->parent )
521  newShape->parent = shape->parent;
522  else
523  newShape->parent = shape;
524 
525  inputShapes.append( newShape );
526 
527  if ( imax == fps )
528  imax = fpe;
529  else
530  imax = fps;
531 
532  newShape = shape->extractShape( nbPtSh2, imax, imin, fps, fpe, fptx, fpty ).release();
533 
534  if ( shape->parent )
535  newShape->parent = shape->parent;
536  else
537  newShape->parent = shape;
538 
539  inputShapes.append( newShape );
540 
541  if ( shape->parent )
542  delete shape;
543  }
544  }
545  else
546  {
547  outputShapes.append( shape );
548  }
549  delete[] pts;
550  }
551 }
552 
553 void PointSet::extendLineByDistance( double startDistance, double endDistance, double smoothDistance )
554 {
555  if ( nbPoints < 2 )
556  return;
557 
558  double x0 = x[0];
559  double y0 = y[0];
560  if ( startDistance > 0 )
561  {
562  // trace forward by smoothDistance
563  double x1 = x[1];
564  double y1 = y[1];
565 
566  double distanceConsumed = 0;
567  double lastX = x0;
568  double lastY = y0;
569  for ( int i = 1; i < nbPoints; ++i )
570  {
571  double thisX = x[i];
572  double thisY = y[i];
573  const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
574  distanceConsumed += thisSegmentLength;
575  if ( distanceConsumed >= smoothDistance )
576  {
577  double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
578  x1 = lastX + c * ( thisX - lastX );
579  y1 = lastY + c * ( thisY - lastY );
580  break;
581  }
582  lastX = thisX;
583  lastY = thisY;
584  }
585 
586  const double distance = std::sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
587  const double extensionFactor = ( startDistance + distance ) / distance;
588  const QgsPointXY newStart = QgsGeometryUtils::interpolatePointOnLine( x1, y1, x0, y0, extensionFactor );
589  x0 = newStart.x();
590  y0 = newStart.y();
591  // defer actually changing the stored start until we've calculated the new end point
592  }
593 
594  if ( endDistance > 0 )
595  {
596  double xend0 = x[nbPoints - 1];
597  double yend0 = y[nbPoints - 1];
598  double xend1 = x[nbPoints - 2];
599  double yend1 = y[nbPoints - 2];
600 
601  // trace backward by smoothDistance
602  double distanceConsumed = 0;
603  double lastX = x0;
604  double lastY = y0;
605  for ( int i = nbPoints - 2; i >= 0; --i )
606  {
607  double thisX = x[i];
608  double thisY = y[i];
609  const double thisSegmentLength = std::sqrt( ( thisX - lastX ) * ( thisX - lastX ) + ( thisY - lastY ) * ( thisY - lastY ) );
610  distanceConsumed += thisSegmentLength;
611  if ( distanceConsumed >= smoothDistance )
612  {
613  double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
614  xend1 = lastX + c * ( thisX - lastX );
615  yend1 = lastY + c * ( thisY - lastY );
616  break;
617  }
618  lastX = thisX;
619  lastY = thisY;
620  }
621 
622  const double distance = std::sqrt( ( xend1 - xend0 ) * ( xend1 - xend0 ) + ( yend1 - yend0 ) * ( yend1 - yend0 ) );
623  const double extensionFactor = ( endDistance + distance ) / distance;
624  const QgsPointXY newEnd = QgsGeometryUtils::interpolatePointOnLine( xend1, yend1, xend0, yend0, extensionFactor );
625  x.emplace_back( newEnd.x() );
626  y.emplace_back( newEnd.y() );
627  nbPoints++;
628  }
629 
630  if ( startDistance > 0 )
631  {
632  x.insert( x.begin(), x0 );
633  y.insert( y.begin(), y0 );
634  nbPoints++;
635  }
636 
637  invalidateGeos();
638 }
639 
641 {
642  int i;
643  int j;
644 
645  double bbox[4]; // xmin, ymin, xmax, ymax
646 
647  double alpha;
648  int alpha_d;
649 
650  double alpha_seg;
651 
652  double dref;
653  double d1, d2;
654 
655  double bb[16]; // {ax, ay, bx, by, cx, cy, dx, dy, ex, ey, fx, fy, gx, gy, hx, hy}}
656 
657  double cp;
658  double best_cp;
659  double distNearestPoint;
660 
661  double area;
662  double width;
663  double length;
664 
665  double best_area = std::numeric_limits<double>::max();
666  double best_alpha = -1;
667  double best_bb[16];
668  double best_length = 0;
669  double best_width = 0;
670 
671 
672  bbox[0] = std::numeric_limits<double>::max();
673  bbox[1] = std::numeric_limits<double>::max();
674  bbox[2] = std::numeric_limits<double>::lowest();
675  bbox[3] = std::numeric_limits<double>::lowest();
676 
677  for ( i = 0; i < cHullSize; i++ )
678  {
679  if ( x[cHull[i]] < bbox[0] )
680  bbox[0] = x[cHull[i]];
681 
682  if ( x[cHull[i]] > bbox[2] )
683  bbox[2] = x[cHull[i]];
684 
685  if ( y[cHull[i]] < bbox[1] )
686  bbox[1] = y[cHull[i]];
687 
688  if ( y[cHull[i]] > bbox[3] )
689  bbox[3] = y[cHull[i]];
690  }
691 
692 
693  dref = bbox[2] - bbox[0];
694 
695  for ( alpha_d = 0; alpha_d < 90; alpha_d++ )
696  {
697  alpha = alpha_d * M_PI / 180.0;
698  d1 = std::cos( alpha ) * dref;
699  d2 = std::sin( alpha ) * dref;
700 
701  bb[0] = bbox[0];
702  bb[1] = bbox[3]; // ax, ay
703 
704  bb[4] = bbox[0];
705  bb[5] = bbox[1]; // cx, cy
706 
707  bb[8] = bbox[2];
708  bb[9] = bbox[1]; // ex, ey
709 
710  bb[12] = bbox[2];
711  bb[13] = bbox[3]; // gx, gy
712 
713 
714  bb[2] = bb[0] + d1;
715  bb[3] = bb[1] + d2; // bx, by
716  bb[6] = bb[4] - d2;
717  bb[7] = bb[5] + d1; // dx, dy
718  bb[10] = bb[8] - d1;
719  bb[11] = bb[9] - d2; // fx, fy
720  bb[14] = bb[12] + d2;
721  bb[15] = bb[13] - d1; // hx, hy
722 
723  // adjust all points
724  for ( i = 0; i < 16; i += 4 )
725  {
726 
727  alpha_seg = ( ( i / 4 > 0 ? ( i / 4 ) - 1 : 3 ) ) * M_PI_2 + alpha;
728 
729  best_cp = std::numeric_limits<double>::max();
730  for ( j = 0; j < nbPoints; j++ )
731  {
732  cp = GeomFunction::cross_product( bb[i + 2], bb[i + 3], bb[i], bb[i + 1], x[cHull[j]], y[cHull[j]] );
733  if ( cp < best_cp )
734  {
735  best_cp = cp;
736  }
737  }
738 
739  distNearestPoint = best_cp / dref;
740 
741  d1 = std::cos( alpha_seg ) * distNearestPoint;
742  d2 = std::sin( alpha_seg ) * distNearestPoint;
743 
744  bb[i] += d1; // x
745  bb[i + 1] += d2; // y
746  bb[i + 2] += d1; // x
747  bb[i + 3] += d2; // y
748  }
749 
750  // compute and compare AREA
751  width = GeomFunction::cross_product( bb[6], bb[7], bb[4], bb[5], bb[12], bb[13] ) / dref;
752  length = GeomFunction::cross_product( bb[2], bb[3], bb[0], bb[1], bb[8], bb[9] ) / dref;
753 
754  area = width * length;
755 
756  if ( area < 0 )
757  area *= -1;
758 
759 
760  if ( best_area - area > EPSILON )
761  {
762  best_area = area;
763  best_length = length;
764  best_width = width;
765  best_alpha = alpha;
766  memcpy( best_bb, bb, sizeof( double ) * 16 );
767  }
768  }
769 
770  // best bbox is defined
771 
772  CHullBox finalBb;
773 
774  for ( i = 0; i < 16; i = i + 4 )
775  {
776  GeomFunction::computeLineIntersection( best_bb[i], best_bb[i + 1], best_bb[i + 2], best_bb[i + 3],
777  best_bb[( i + 4 ) % 16], best_bb[( i + 5 ) % 16], best_bb[( i + 6 ) % 16], best_bb[( i + 7 ) % 16],
778  &finalBb.x[int ( i / 4 )], &finalBb.y[int ( i / 4 )] );
779  }
780 
781  finalBb.alpha = best_alpha;
782  finalBb.width = best_width;
783  finalBb.length = best_length;
784 
785  return finalBb;
786 }
787 
788 double PointSet::minDistanceToPoint( double px, double py, double *rx, double *ry ) const
789 {
790  if ( !mGeos )
791  createGeosGeom();
792 
793  if ( !mGeos )
794  return 0;
795 
796  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
797  try
798  {
799 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
800  geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, px, py ) );
801 #else
802  GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
803  GEOSCoordSeq_setX_r( geosctxt, coord, 0, px );
804  GEOSCoordSeq_setY_r( geosctxt, coord, 0, py );
805  geos::unique_ptr geosPt( GEOSGeom_createPoint_r( geosctxt, coord ) );
806 #endif
807  int type = GEOSGeomTypeId_r( geosctxt, mGeos );
808  const GEOSGeometry *extRing = nullptr;
809  if ( type != GEOS_POLYGON )
810  {
811  extRing = mGeos;
812  }
813  else
814  {
815  //for polygons, we want distance to exterior ring (not an interior point)
816  extRing = GEOSGetExteriorRing_r( geosctxt, mGeos );
817  }
818  geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosctxt, extRing, geosPt.get() ) );
819  double nx;
820  double ny;
821 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
822  ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
823 #else
824  ( void )GEOSCoordSeq_getX_r( geosctxt, nearestCoord.get(), 0, &nx );
825  ( void )GEOSCoordSeq_getY_r( geosctxt, nearestCoord.get(), 0, &ny );
826 #endif
827 
828  if ( rx )
829  *rx = nx;
830  if ( ry )
831  *ry = ny;
832 
833  return GeomFunction::dist_euc2d_sq( px, py, nx, ny );
834  }
835  catch ( GEOSException &e )
836  {
837  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
838  return 0;
839  }
840 }
841 
842 void PointSet::getCentroid( double &px, double &py, bool forceInside ) const
843 {
844  if ( !mGeos )
845  createGeosGeom();
846 
847  if ( !mGeos )
848  return;
849 
850  try
851  {
852  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
853  geos::unique_ptr centroidGeom( GEOSGetCentroid_r( geosctxt, mGeos ) );
854  if ( centroidGeom )
855  {
856  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, centroidGeom.get() );
857 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
858  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
859 #else
860  GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
861  GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
862 #endif
863  }
864 
865  // check if centroid inside in polygon
866  if ( forceInside && !containsPoint( px, py ) )
867  {
868  geos::unique_ptr pointGeom( GEOSPointOnSurface_r( geosctxt, mGeos ) );
869 
870  if ( pointGeom )
871  {
872  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
873 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
874  GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
875 #else
876  GEOSCoordSeq_getX_r( geosctxt, coordSeq, 0, &px );
877  GEOSCoordSeq_getY_r( geosctxt, coordSeq, 0, &py );
878 #endif
879  }
880  }
881  }
882  catch ( GEOSException &e )
883  {
884  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
885  return;
886  }
887 }
888 
889 bool PointSet::boundingBoxIntersects( const PointSet *other ) const
890 {
891  double x1 = ( xmin > other->xmin ? xmin : other->xmin );
892  double x2 = ( xmax < other->xmax ? xmax : other->xmax );
893  if ( x1 > x2 )
894  return false;
895  double y1 = ( ymin > other->ymin ? ymin : other->ymin );
896  double y2 = ( ymax < other->ymax ? ymax : other->ymax );
897  return y1 <= y2;
898 }
899 
900 void PointSet::getPointByDistance( double *d, double *ad, double dl, double *px, double *py )
901 {
902  int i;
903  double dx, dy, di;
904  double distr;
905 
906  i = 0;
907  if ( dl >= 0 )
908  {
909  while ( i < nbPoints && ad[i] <= dl ) i++;
910  i--;
911  }
912 
913  if ( i < nbPoints - 1 )
914  {
915  if ( dl < 0 )
916  {
917  dx = x[nbPoints - 1] - x[0];
918  dy = y[nbPoints - 1] - y[0];
919  di = std::sqrt( dx * dx + dy * dy );
920  }
921  else
922  {
923  dx = x[i + 1] - x[i];
924  dy = y[i + 1] - y[i];
925  di = d[i];
926  }
927 
928  distr = dl - ad[i];
929  *px = x[i] + dx * distr / di;
930  *py = y[i] + dy * distr / di;
931  }
932  else // just select last point...
933  {
934  *px = x[i];
935  *py = y[i];
936  }
937 }
938 
939 const GEOSGeometry *PointSet::geos() const
940 {
941  if ( !mGeos )
942  createGeosGeom();
943 
944  return mGeos;
945 }
946 
947 double PointSet::length() const
948 {
949  if ( mLength >= 0 )
950  return mLength;
951 
952  if ( !mGeos )
953  createGeosGeom();
954 
955  if ( !mGeos )
956  return -1;
957 
958  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
959 
960  try
961  {
962  ( void )GEOSLength_r( geosctxt, mGeos, &mLength );
963  return mLength;
964  }
965  catch ( GEOSException &e )
966  {
967  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
968  return -1;
969  }
970 }
971 
972 double PointSet::area() const
973 {
974  if ( mArea >= 0 )
975  return mArea;
976 
977  if ( !mGeos )
978  createGeosGeom();
979 
980  if ( !mGeos )
981  return -1;
982 
983  GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
984 
985  try
986  {
987  ( void )GEOSArea_r( geosctxt, mGeos, &mArea );
988  mArea = std::fabs( mArea );
989  return mArea;
990  }
991  catch ( GEOSException &e )
992  {
993  QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
994  return -1;
995  }
996 }
997 
998 bool PointSet::isClosed() const
999 {
1000  return qgsDoubleNear( x[0], x[nbPoints - 1] ) && qgsDoubleNear( y[0], y[nbPoints - 1] );
1001 }
CHullBox compute_chull_bbox()
Computes a con???? hull.
Definition: pointset.cpp:640
void invalidateGeos()
Definition: pointset.cpp:179
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:246
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.
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
std::vector< double > x
Definition: pointset.h:201
void createGeosGeom() const
Definition: pointset.cpp:117
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.
bool isClosed() const
Returns true if pointset is closed.
Definition: pointset.cpp:998
double mLength
Definition: pointset.h:217
static int convexHullId(int *id, const std::vector< double > &x, const std::vector< double > &y, int n, int *&cHull)
Compute the convex hull in O(n·log(n))
std::unique_ptr< PointSet > extractShape(int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty)
Does...
Definition: pointset.cpp:214
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2872
static double cross_product(double x1, double y1, double x2, double y2, double x3, double y3)
Definition: geomfunction.h:61
double alpha
Definition: pointset.h:60
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:889
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
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.
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:900
static void splitPolygons(QLinkedList< PointSet *> &inputShapes, QLinkedList< PointSet *> &outputShapes, double xrm, double yrm)
Split a concave shape into several convex shapes.
Definition: pointset.cpp:276
PointSet * parent
Definition: pointset.h:214
virtual ~PointSet()
Definition: pointset.cpp:192
double ymax
Definition: pointset.h:232
double xmin
Definition: pointset.h:229
PointSet * holeOf
Definition: pointset.h:213
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
double ymin
Definition: pointset.h:231
int * cHull
Definition: pointset.h:208
static double dist_euc2d(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:66
static QgsPointXY interpolatePointOnLine(double x1, double y1, double x2, double y2, double fraction)
Interpolates the position of a point a fraction of the way along the line from (x1, y1) to (x2, y2).
void deleteCoords()
Definition: pointset.cpp:208
double x
Definition: qgspointxy.h:47
double length
Definition: pointset.h:63
double area() const
Returns area of polygon geometry.
Definition: pointset.cpp:972
std::unique_ptr< PointSet > clone() const
Returns a copy of the point set.
Definition: pointset.cpp:241
double length() const
Returns length of line geometry.
Definition: pointset.cpp:947
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:553
double x[4]
Definition: pointset.h:57
#define EPSILON
Definition: util.h:78
GEOSGeometry * mGeos
Definition: pointset.h:205
double width
Definition: pointset.h:62
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:167
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition: qgsgeos.h:94
static double dist_euc2d_sq(double x1, double y1, double x2, double y2)
Definition: geomfunction.h:71
double mArea
Definition: pointset.h:216
double y[4]
Definition: pointset.h:58
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition: pointset.cpp:842
const GEOSGeometry * geos() const
Returns the point set&#39;s GEOS geometry.
Definition: pointset.cpp:939
double xmax
Definition: pointset.h:230
std::vector< double > y
Definition: pointset.h:202
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:788
bool mOwnsGeom
Definition: pointset.h:206
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:271