QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsgeometryvalidator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgeometryvalidator.cpp - geometry validation thread
3  -------------------------------------------------------------------
4 Date : 03.01.2012
5 Copyright : (C) 2012 by Juergen E. Fischer
6 email : jef at norbit dot de
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgis.h"
17 #include "qgsgeometryvalidator.h"
18 #include "qgsgeometry.h"
19 #include "qgslogger.h"
20 
21 #include <QSettings>
22 
24  : QThread()
25  , mErrors( errors )
26  , mStop( false )
27  , mErrorCount( 0 )
28 {
29  Q_ASSERT( g );
30  if ( g )
31  mG = *g;
32 }
33 
35 {
36  stop();
37  wait();
38 }
39 
41 {
42  mStop = true;
43 }
44 
45 void QgsGeometryValidator::checkRingIntersections(
46  int p0, int i0, const QgsPolyline &ring0,
47  int p1, int i1, const QgsPolyline &ring1 )
48 {
49  for ( int i = 0; !mStop && i < ring0.size() - 1; i++ )
50  {
51  QgsVector v = ring0[i+1] - ring0[i];
52 
53  for ( int j = 0; !mStop && j < ring1.size() - 1; j++ )
54  {
55  QgsVector w = ring1[j+1] - ring1[j];
56 
57  QgsPoint s;
58  if ( intersectLines( ring0[i], v, ring1[j], w, s ) )
59  {
60  double d = -distLine2Point( ring0[i], v.perpVector(), s );
61 
62  if ( d >= 0 && d <= v.length() )
63  {
64  d = -distLine2Point( ring1[j], w.perpVector(), s );
65  if ( d > 0 && d < w.length() &&
66  ring0[i+1] != ring1[j+1] && ring0[i+1] != ring1[j] &&
67  ring0[i+0] != ring1[j+1] && ring0[i+0] != ring1[j] )
68  {
69  QString msg = QObject::tr( "segment %1 of ring %2 of polygon %3 intersects segment %4 of ring %5 of polygon %6 at %7" )
70  .arg( i0 ).arg( i ).arg( p0 )
71  .arg( i1 ).arg( j ).arg( p1 )
72  .arg( s.toString() );
73  QgsDebugMsg( msg );
74  emit errorFound( QgsGeometry::Error( msg, s ) );
75  mErrorCount++;
76  }
77  }
78  }
79  }
80  }
81 }
82 
83 void QgsGeometryValidator::validatePolyline( int i, QgsPolyline line, bool ring )
84 {
85  if ( ring )
86  {
87  if ( line.size() < 4 )
88  {
89  QString msg = QObject::tr( "ring %1 with less than four points" ).arg( i );
90  QgsDebugMsg( msg );
91  emit errorFound( QgsGeometry::Error( msg ) );
92  mErrorCount++;
93  return;
94  }
95 
96  if ( line[0] != line[ line.size()-1 ] )
97  {
98  QString msg = QObject::tr( "ring %1 not closed" ).arg( i );
99  QgsDebugMsg( msg );
100  emit errorFound( QgsGeometry::Error( msg ) );
101  mErrorCount++;
102  return;
103  }
104  }
105  else if ( line.size() < 2 )
106  {
107  QString msg = QObject::tr( "line %1 with less than two points" ).arg( i );
108  QgsDebugMsg( msg );
109  emit errorFound( QgsGeometry::Error( msg ) );
110  mErrorCount++;
111  return;
112  }
113 
114  int j = 0;
115  while ( j < line.size() - 1 )
116  {
117  int n = 0;
118  while ( j < line.size() - 1 && line[j] == line[j+1] )
119  {
120  line.remove( j );
121  n++;
122  }
123 
124  if ( n > 0 )
125  {
126  QString msg = QObject::tr( "line %1 contains %n duplicate node(s) at %2", "number of duplicate nodes", n ).arg( i ).arg( j );
127  QgsDebugMsg( msg );
128  emit errorFound( QgsGeometry::Error( msg, line[j] ) );
129  mErrorCount++;
130  }
131 
132  j++;
133  }
134 
135  for ( j = 0; !mStop && j < line.size() - 3; j++ )
136  {
137  QgsVector v = line[j+1] - line[j];
138  double vl = v.length();
139 
140  int n = ( j == 0 && ring ) ? line.size() - 2 : line.size() - 1;
141 
142  for ( int k = j + 2; !mStop && k < n; k++ )
143  {
144  QgsVector w = line[k+1] - line[k];
145 
146  QgsPoint s;
147  if ( !intersectLines( line[j], v, line[k], w, s ) )
148  continue;
149 
150  double d = 0.0;
151  try
152  {
153  d = -distLine2Point( line[j], v.perpVector(), s );
154  }
155  catch ( QgsException & e )
156  {
157  Q_UNUSED( e );
158  QgsDebugMsg( "Error validating: " + e.what() );
159  continue;
160  }
161  if ( d < 0 || d > vl )
162  continue;
163 
164  try
165  {
166  d = -distLine2Point( line[k], w.perpVector(), s );
167  }
168  catch ( QgsException & e )
169  {
170  Q_UNUSED( e );
171  QgsDebugMsg( "Error validating: " + e.what() );
172  continue;
173  }
174 
175  if ( d <= 0 || d >= w.length() )
176  continue;
177 
178  QString msg = QObject::tr( "segments %1 and %2 of line %3 intersect at %4" ).arg( j ).arg( k ).arg( i ).arg( s.toString() );
179  QgsDebugMsg( msg );
180  emit errorFound( QgsGeometry::Error( msg, s ) );
181  mErrorCount++;
182  }
183  }
184 }
185 
186 void QgsGeometryValidator::validatePolygon( int idx, const QgsPolygon &polygon )
187 {
188  // check if holes are inside polygon
189  for ( int i = 1; !mStop && i < polygon.size(); i++ )
190  {
191  if ( !ringInRing( polygon[i], polygon[0] ) )
192  {
193  QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i ).arg( idx );
194  QgsDebugMsg( msg );
195  emit errorFound( QgsGeometry::Error( msg ) );
196  mErrorCount++;
197  }
198  }
199 
200  // check holes for intersections
201  for ( int i = 1; !mStop && i < polygon.size(); i++ )
202  {
203  for ( int j = i + 1; !mStop && j < polygon.size(); j++ )
204  {
205  checkRingIntersections( idx, i, polygon[i], idx, j, polygon[j] );
206  }
207  }
208 
209  // check if rings are self-intersecting
210  for ( int i = 0; !mStop && i < polygon.size(); i++ )
211  {
212  validatePolyline( i, polygon[i], true );
213  }
214 }
215 
217 {
218  mErrorCount = 0;
219 #if defined(GEOS_VERSION_MAJOR) && defined(GEOS_VERSION_MINOR) && \
220  ( (GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR>=3) || GEOS_VERSION_MAJOR>3)
221  QSettings settings;
222  if ( settings.value( "/qgis/digitizing/validate_geometries", 1 ).toInt() == 2 )
223  {
224  char *r = nullptr;
225  const GEOSGeometry *g0 = mG.asGeos();
226  GEOSContextHandle_t handle = QgsGeometry::getGEOSHandler();
227  if ( !g0 )
228  {
229  emit errorFound( QgsGeometry::Error( QObject::tr( "GEOS error:could not produce geometry for GEOS (check log window)" ) ) );
230  }
231  else
232  {
233  GEOSGeometry *g1 = nullptr;
234  if ( GEOSisValidDetail_r( handle, g0, GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE, &r, &g1 ) != 1 )
235  {
236  if ( g1 )
237  {
238  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( handle, g1 );
239 
240  unsigned int n;
241  if ( GEOSCoordSeq_getSize_r( handle, cs, &n ) && n == 1 )
242  {
243  double x, y;
244  GEOSCoordSeq_getX_r( handle, cs, 0, &x );
245  GEOSCoordSeq_getY_r( handle, cs, 0, &y );
246  emit errorFound( QgsGeometry::Error( QObject::tr( "GEOS error:%1" ).arg( r ), QgsPoint( x, y ) ) );
247  mErrorCount++;
248  }
249 
250  GEOSGeom_destroy_r( handle, g1 );
251  }
252  else
253  {
254  emit errorFound( QgsGeometry::Error( QObject::tr( "GEOS error:%1" ).arg( r ) ) );
255  mErrorCount++;
256  }
257 
258  GEOSFree_r( handle, r );
259  }
260  }
261 
262  return;
263  }
264 #endif
265 
266  QgsDebugMsg( "validation thread started." );
267 
268  switch ( mG.wkbType() )
269  {
270  case QGis::WKBPoint:
271  case QGis::WKBPoint25D:
272  case QGis::WKBMultiPoint:
274  break;
275 
276  case QGis::WKBLineString:
278  validatePolyline( 0, mG.asPolyline() );
279  break;
280 
283  {
285  for ( int i = 0; !mStop && i < mp.size(); i++ )
286  validatePolyline( i, mp[i] );
287  }
288  break;
289 
290  case QGis::WKBPolygon:
291  case QGis::WKBPolygon25D:
292  {
293  validatePolygon( 0, mG.asPolygon() );
294  }
295  break;
296 
299  {
301  for ( int i = 0; !mStop && i < mp.size(); i++ )
302  {
303  validatePolygon( i, mp[i] );
304  }
305 
306  for ( int i = 0; !mStop && i < mp.size(); i++ )
307  {
308  if ( mp[i].isEmpty() )
309  {
310  emit errorFound( QgsGeometry::Error( QObject::tr( "polygon %1 has no rings" ).arg( i ) ) );
311  mErrorCount++;
312  continue;
313  }
314 
315  for ( int j = i + 1; !mStop && j < mp.size(); j++ )
316  {
317  if ( mp[j].isEmpty() )
318  continue;
319 
320  if ( ringInRing( mp[i][0], mp[j][0] ) )
321  {
322  emit errorFound( QgsGeometry::Error( QObject::tr( "polygon %1 inside polygon %2" ).arg( i ).arg( j ) ) );
323  mErrorCount++;
324  }
325  else if ( ringInRing( mp[j][0], mp[i][0] ) )
326  {
327  emit errorFound( QgsGeometry::Error( QObject::tr( "polygon %1 inside polygon %2" ).arg( j ).arg( i ) ) );
328  mErrorCount++;
329  }
330  else
331  {
332  checkRingIntersections( i, 0, mp[i][0], j, 0, mp[j][0] );
333  }
334  }
335  }
336  }
337  break;
338 
339  case QGis::WKBNoGeometry:
340  case QGis::WKBUnknown:
341  QgsDebugMsg( QObject::tr( "Unknown geometry type" ) );
342  emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( mG.wkbType() ) ) );
343  mErrorCount++;
344  break;
345  }
346 
347  QgsDebugMsg( "validation finished." );
348 
349  if ( mStop )
350  {
351  emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry validation was aborted." ) ) );
352  }
353  else if ( mErrorCount > 0 )
354  {
355  emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry has %1 errors." ).arg( mErrorCount ) ) );
356  }
357 #if 0
358  else
359  {
360  emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry is valid." ) ) );
361  }
362 #endif
363 }
364 
366 {
367  if ( mErrors )
368  *mErrors << e;
369 }
370 
372 {
373  QgsGeometryValidator *gv = new QgsGeometryValidator( g, &errors );
374  connect( gv, SIGNAL( errorFound( QgsGeometry::Error ) ), gv, SLOT( addError( QgsGeometry::Error ) ) );
375  gv->run();
376  gv->wait();
377 }
378 
379 //
380 // distance of point q from line through p in direction v
381 // return >0 => q lies left of the line
382 // <0 => q lies right of the line
383 //
384 double QgsGeometryValidator::distLine2Point( const QgsPoint& p, QgsVector v, const QgsPoint& q )
385 {
386  if ( qgsDoubleNear( v.length(), 0 ) )
387  {
388  throw QgsException( QObject::tr( "invalid line" ) );
389  }
390 
391  return ( v.x()*( q.y() - p.y() ) - v.y()*( q.x() - p.x() ) ) / v.length();
392 }
393 
394 bool QgsGeometryValidator::intersectLines( const QgsPoint& p, QgsVector v, const QgsPoint& q, QgsVector w, QgsPoint &s )
395 {
396  double d = v.y() * w.x() - v.x() * w.y();
397 
398  if ( qgsDoubleNear( d, 0 ) )
399  return false;
400 
401  double dx = q.x() - p.x();
402  double dy = q.y() - p.y();
403  double k = ( dy * w.x() - dx * w.y() ) / d;
404 
405  s = p + v * k;
406 
407  return true;
408 }
409 
410 bool QgsGeometryValidator::pointInRing( const QgsPolyline &ring, const QgsPoint &p )
411 {
412  bool inside = false;
413  int j = ring.size() - 1;
414 
415  for ( int i = 0; !mStop && i < ring.size(); i++ )
416  {
417  if ( qgsDoubleNear( ring[i].x(), p.x() ) && qgsDoubleNear( ring[i].y(), p.y() ) )
418  return true;
419 
420  if (( ring[i].y() < p.y() && ring[j].y() >= p.y() ) ||
421  ( ring[j].y() < p.y() && ring[i].y() >= p.y() ) )
422  {
423  if ( ring[i].x() + ( p.y() - ring[i].y() ) / ( ring[j].y() - ring[i].y() )*( ring[j].x() - ring[i].x() ) <= p.x() )
424  inside = !inside;
425  }
426 
427  j = i;
428  }
429 
430  return inside;
431 }
432 
433 bool QgsGeometryValidator::ringInRing( const QgsPolyline &inside, const QgsPolyline &outside )
434 {
435  for ( int i = 0; !mStop && i < inside.size(); i++ )
436  {
437  if ( !pointInRing( outside, inside[i] ) )
438  return false;
439  }
440 
441  return true;
442 }
QgsPolygon asPolygon() const
Return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
QgsMultiPolyline asMultiPolyline() const
Return contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list.
QGis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void addError(const QgsGeometry::Error &)
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
QgsPolyline asPolyline() const
Return contents of the geometry as a polyline if wkbType is WKBLineString, otherwise an empty list...
double y() const
Get the y value of the point.
Definition: qgspoint.h:193
QString what() const
Definition: qgsexception.h:36
int toInt(bool *ok) const
void remove(int i)
void errorFound(const QgsGeometry::Error &)
A class to represent a point.
Definition: qgspoint.h:117
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:134
double length() const
Returns the length of the vector.
Definition: qgspoint.cpp:63
A class to represent a vector.
Definition: qgspoint.h:32
static void validateGeometry(const QgsGeometry *g, QList< QgsGeometry::Error > &errors)
Validate geometry and produce a list of geometry errors.
QVariant value(const QString &key, const QVariant &defaultValue) const
QgsVector perpVector() const
Returns the perpendicular vector to this vector (rotated 90 degrees counter-clockwise) ...
Definition: qgspoint.cpp:78
static GEOSContextHandle_t getGEOSHandler()
Return GEOS context handle.
bool wait(unsigned long time)
QgsMultiPolygon asMultiPolygon() const
Return contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty ...
const GEOSGeometry * asGeos(double precision=0) const
Returns a geos geometry.
double x() const
Returns the vector&#39;s x-component.
Definition: qgspoint.cpp:68
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int size() const
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QgsGeometryValidator(const QgsGeometry *g, QList< QgsGeometry::Error > *errors=nullptr)
Constructor.
double y() const
Returns the vector&#39;s y-component.
Definition: qgspoint.cpp:73
Defines a qgis exception class.
Definition: qgsexception.h:25
double x() const
Get the x value of the point.
Definition: qgspoint.h:185