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