void QgsGeometryDuplicateNodesCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const { Q_UNUSED( messages ) QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap(); QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry().constGet(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing ) { int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, iPart, iRing ); if ( nVerts < 2 ) continue; for ( int iVert = nVerts - 1, jVert = 0; jVert < nVerts; iVert = jVert++ ) { QgsPoint pi = geom->vertexAt( QgsVertexId( iPart, iRing, iVert ) ); QgsPoint pj = geom->vertexAt( QgsVertexId( iPart, iRing, jVert ) ); if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) < mContext->tolerance ) { errors.append( new QgsGeometryCheckError( this, layerFeature, pj, QgsVertexId( iPart, iRing, jVert ) ) ); } } } } } }
void QgsGeometryOverlapCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { double overlapThreshold = mThresholdMapUnits; QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter, true ); QList<QString> layerIds = featureIds.keys(); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA ) { // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB layerIds.removeOne( layerFeatureA.layer().id() ); QgsRectangle bboxA = layerFeatureA.geometry()->boundingBox(); std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry(), mContext->tolerance ); if ( !geomEngineA->isValid() ) { messages.append( tr( "Overlap check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) ); continue; } QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( mContext->featurePools, QList<QString>() << layerFeatureA.layer().id() << layerIds, bboxA, mCompatibleGeometryTypes ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB ) { // > : only report overlaps within same layer once if ( layerFeatureA.layer().id() == layerFeatureB.layer().id() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() ) { continue; } QString errMsg; if ( geomEngineA->overlaps( layerFeatureB.geometry(), &errMsg ) ) { QgsAbstractGeometry *interGeom = geomEngineA->intersection( layerFeatureB.geometry() ); if ( interGeom && !interGeom->isEmpty() ) { QgsGeometryCheckerUtils::filter1DTypes( interGeom ); for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart ) { QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom, iPart ); double area = interPart->area(); if ( area > mContext->reducedTolerance && area < overlapThreshold ) { errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, interPart->clone(), interPart->centroid(), area, layerFeatureB ) ); } } } else if ( !errMsg.isEmpty() ) { messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id() ).arg( layerFeatureB.id() ).arg( errMsg ) ); } delete interGeom; } } } }
void QgsGeometryLineIntersectionCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter, true ); QList<QString> layerIds = featureIds.keys(); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA ) { // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB layerIds.removeOne( layerFeatureA.layer().id() ); const QgsAbstractGeometry *geom = layerFeatureA.geometry(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { const QgsLineString *line = dynamic_cast<const QgsLineString *>( QgsGeometryCheckerUtils::getGeomPart( geom, iPart ) ); if ( !line ) { // Should not happen continue; } // Check whether the line intersects with any other lines QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( mContext->featurePools, QList<QString>() << layerFeatureA.layer().id() << layerIds, line->boundingBox(), {QgsWkbTypes::LineGeometry} ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB ) { // > : only report intersections within same layer once if ( layerFeatureA.layer().id() == layerFeatureB.layer().id() && layerFeatureB.feature().id() > layerFeatureA.feature().id() ) { continue; } const QgsAbstractGeometry *testGeom = layerFeatureB.geometry(); for ( int jPart = 0, mParts = testGeom->partCount(); jPart < mParts; ++jPart ) { // Skip current feature part, only report intersections within same part once if ( layerFeatureB.feature().id() == layerFeatureA.feature().id() && iPart >= jPart ) { continue; } const QgsLineString *testLine = dynamic_cast<const QgsLineString *>( QgsGeometryCheckerUtils::getGeomPart( testGeom, jPart ) ); if ( !testLine ) { continue; } const QList< QgsPoint > intersections = QgsGeometryCheckerUtils::lineIntersections( line, testLine, mContext->tolerance ); for ( const QgsPoint &inter : intersections ) { errors.append( new QgsGeometryCheckError( this, layerFeatureA, inter, QgsVertexId( iPart ), layerFeatureB.id() ) ); } } } } } }
void QgsGeometryFollowBoundariesCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { if ( !mIndex || !mCheckLayer ) { return; } QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; featureIds.remove( mCheckLayer->id() ); // Don't check layer against itself QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry(); // The geometry to crs of the check layer QgsCoordinateTransform crst = QgsCoordinateTransformCache::instance()->transform( layerFeature.layer().crs().authid(), mCheckLayer->crs().authid() ); QScopedPointer<QgsAbstractGeometry> geomt( geom->clone() ); geomt->transform( crst ); QSharedPointer<QgsGeometryEngine> geomEngine = QgsGeometryCheckerUtils::createGeomEngine( geomt.data(), mContext->tolerance ); // Get potential reference features QgsRectangle searchBounds = geomt->boundingBox(); searchBounds.grow( mContext->tolerance ); QgsFeatureIds refFeatureIds = mIndex->intersects( searchBounds ).toSet(); QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( refFeatureIds ).setSubsetOfAttributes( QgsAttributeList() ); QgsFeatureIterator refFeatureIt = mCheckLayer->getFeatures( refFeatureRequest ); if ( refFeatureIds.isEmpty() ) { // If no potential reference features are found, the geometry is definitely not following boundaries of reference layer features errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( geom->centroid() ) ) ); } else { // All reference features must be either contained or disjoint from tested geometry QgsFeature refFeature; while ( refFeatureIt.nextFeature( refFeature ) ) { QgsAbstractGeometry *refGeom = refFeature.geometry().geometry(); QSharedPointer<QgsGeometryEngine> refgeomEngine = QgsGeometryCheckerUtils::createGeomEngine( refGeom, mContext->tolerance ); QScopedPointer<QgsAbstractGeometry> reducedRefGeom( refgeomEngine->buffer( -mContext->tolerance, 0 ) ); if ( !( geomEngine->contains( reducedRefGeom.data() ) || geomEngine->disjoint( reducedRefGeom.data() ) ) ) { errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( geom->centroid() ) ) ); break; } } } } }
void QgsGeometryPointCoveredByLineCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const { Q_UNUSED( messages ) QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap(); QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext, true ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry().constGet(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { const QgsPoint *point = dynamic_cast<const QgsPoint *>( QgsGeometryCheckerUtils::getGeomPart( geom, iPart ) ); if ( !point ) { // Should not happen continue; } // Check that point lies on a line bool touches = false; QgsRectangle rect( point->x() - mContext->tolerance, point->y() - mContext->tolerance, point->x() + mContext->tolerance, point->y() + mContext->tolerance ); QgsGeometryCheckerUtils::LayerFeatures checkFeatures( featurePools, featureIds.keys(), rect, {QgsWkbTypes::LineGeometry}, mContext ); for ( const QgsGeometryCheckerUtils::LayerFeature &checkFeature : checkFeatures ) { const QgsAbstractGeometry *testGeom = checkFeature.geometry().constGet(); for ( int jPart = 0, mParts = testGeom->partCount(); jPart < mParts; ++jPart ) { const QgsLineString *testLine = dynamic_cast<const QgsLineString *>( QgsGeometryCheckerUtils::getGeomPart( testGeom, jPart ) ); if ( !testLine ) { continue; } if ( QgsGeometryCheckerUtils::pointOnLine( *point, testLine, mContext->tolerance ) ) { touches = true; break; } } if ( touches ) { break; } } if ( touches ) { continue; } errors.append( new QgsGeometryCheckError( this, layerFeature, *point, QgsVertexId( iPart, 0, 0 ) ) ); } } }
void QgsGeometryLineLayerIntersectionCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; featureIds.remove( mCheckLayer ); // Don't check layer against itself QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter, mContext, true ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry().constGet(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { const QgsLineString *line = dynamic_cast<const QgsLineString *>( QgsGeometryCheckerUtils::getGeomPart( geom, iPart ) ); if ( !line ) { // Should not happen continue; } // Check whether the line intersects with any other features of the specified layer QgsGeometryCheckerUtils::LayerFeatures checkFeatures( mContext->featurePools, QStringList() << mCheckLayer, line->boundingBox(), {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}, mContext ); for ( const QgsGeometryCheckerUtils::LayerFeature &checkFeature : checkFeatures ) { const QgsAbstractGeometry *testGeom = checkFeature.geometry().constGet(); for ( int jPart = 0, mParts = testGeom->partCount(); jPart < mParts; ++jPart ) { const QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( testGeom, jPart ); if ( const QgsLineString *testLine = dynamic_cast<const QgsLineString *>( part ) ) { const QList< QgsPoint > intersections = QgsGeometryCheckerUtils::lineIntersections( line, testLine, mContext->tolerance ); for ( const QgsPoint &inter : intersections ) { errors.append( new QgsGeometryCheckError( this, layerFeature, inter, QgsVertexId( iPart ), checkFeature.id() ) ); } } else if ( const QgsPolygon *polygon = dynamic_cast<const QgsPolygon *>( part ) ) { QList< const QgsLineString * > rings = QgsGeometryCheckerUtils::polygonRings( polygon ); for ( const QgsLineString *ring : rings ) { const QList< QgsPoint > intersections = QgsGeometryCheckerUtils::lineIntersections( line, ring, mContext->tolerance ); for ( const QgsPoint &inter : intersections ) { errors.append( new QgsGeometryCheckError( this, layerFeature, inter, QgsVertexId( iPart ), checkFeature.id() ) ); } } } } } } } }
void QgsGeometryTypeCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry(); QgsWkbTypes::Type type = QgsWkbTypes::flatType( geom->wkbType() ); if ( ( mAllowedTypes & ( 1 << type ) ) == 0 ) { errors.append( new QgsGeometryTypeCheckError( this, layerFeature, geom->centroid(), type ) ); } } }
void QgsGeometryAreaCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { double layerToMapUnits = layerFeature.layerToMapUnits(); const QgsAbstractGeometry *geom = layerFeature.geometry(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { double value; const QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( geom, iPart ); if ( checkThreshold( layerToMapUnits, part, value ) ) { errors.append( new QgsGeometryCheckError( this, layerFeature, part->centroid(), QgsVertexId( iPart ), value * layerToMapUnits * layerToMapUnits, QgsGeometryCheckError::ValueArea ) ); } } } }
void QgsGeometrySelfIntersectionCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter, mContext ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry().constGet(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing ) { for ( const QgsGeometryUtils::SelfIntersection &inter : QgsGeometryUtils::selfIntersections( geom, iPart, iRing, mContext->tolerance ) ) { errors.append( new QgsGeometrySelfIntersectionCheckError( this, layerFeature, inter.point, QgsVertexId( iPart, iRing ), inter ) ); } } } } }
void QgsGeometryDegeneratePolygonCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing ) { if ( QgsGeometryCheckerUtils::polyLineSize( geom, iPart, iRing ) < 3 ) { QgsVertexId vidx( iPart, iRing ); errors.append( new QgsGeometryCheckError( this, layerFeature, geom->vertexAt( vidx ), vidx ) ); } } } } }
void QgsGeometryContainedCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter, true ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA ) { QgsRectangle bboxA = layerFeatureA.geometry()->boundingBox(); std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry(), mContext->tolerance ); if ( !geomEngineA->isValid() ) { messages.append( tr( "Contained check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) ); continue; } QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( mContext->featurePools, featureIds.keys(), bboxA, mCompatibleGeometryTypes ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB ) { if ( layerFeatureA == layerFeatureB ) { continue; } std::unique_ptr< QgsGeometryEngine > geomEngineB = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureB.geometry(), mContext->tolerance ); if ( !geomEngineB->isValid() ) { messages.append( tr( "Contained check failed for (%1): the geometry is invalid" ).arg( layerFeatureB.id() ) ); continue; } QString errMsg; // If A contains B and B contains A, it would mean that the geometries are identical, which is covered by the duplicate check if ( geomEngineA->contains( layerFeatureB.geometry(), &errMsg ) && !geomEngineB->contains( layerFeatureA.geometry(), &errMsg ) && errMsg.isEmpty() ) { errors.append( new QgsGeometryContainedCheckError( this, layerFeatureB, layerFeatureB.geometry()->centroid(), layerFeatureA ) ); } else if ( !errMsg.isEmpty() ) { messages.append( tr( "Contained check failed for (%1, %2): %3" ).arg( layerFeatureB.id(), layerFeatureA.id(), errMsg ) ); } } } }
void QgsGeometryHoleCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { const QgsAbstractGeometry *geom = layerFeature.geometry(); for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart ) { const QgsCurvePolygon *poly = dynamic_cast<const QgsCurvePolygon *>( QgsGeometryCheckerUtils::getGeomPart( geom, iPart ) ); if ( !poly ) { continue; } // Rings after the first one are interiors for ( int iRing = 1, nRings = poly->ringCount( iPart ); iRing < nRings; ++iRing ) { QgsPoint pos = poly->interiorRing( iRing - 1 )->centroid(); errors.append( new QgsGeometryCheckError( this, layerFeature, pos, QgsVertexId( iPart, iRing ) ) ); } } } }
void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const { if ( feedback ) feedback->setProgress( feedback->progress() + 1.0 ); QVector<QgsAbstractGeometry *> geomList; QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap(); const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), nullptr, mContext, true ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { geomList.append( layerFeature.geometry().constGet()->clone() ); if ( feedback->isCanceled() ) { qDeleteAll( geomList ); geomList.clear(); break; } } if ( geomList.isEmpty() ) { return; } std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance ); // Create union of geometry QString errMsg; std::unique_ptr<QgsAbstractGeometry> unionGeom( geomEngine->combine( geomList, &errMsg ) ); qDeleteAll( geomList ); if ( !unionGeom ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); return; } // Get envelope of union geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom.get(), mContext->tolerance ); std::unique_ptr<QgsAbstractGeometry> envelope( geomEngine->envelope( &errMsg ) ); if ( !envelope ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); return; } // Buffer envelope geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance ); QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, GEOSBUF_CAP_SQUARE, GEOSBUF_JOIN_MITRE, 4. ); //#spellok //#spellok envelope.reset( bufEnvelope ); // Compute difference between envelope and union to obtain gap polygons geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance ); std::unique_ptr<QgsAbstractGeometry> diffGeom( geomEngine->difference( unionGeom.get(), &errMsg ) ); if ( !diffGeom ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); return; } // For each gap polygon which does not lie on the boundary, get neighboring polygons and add error for ( int iPart = 0, nParts = diffGeom->partCount(); iPart < nParts; ++iPart ) { std::unique_ptr<QgsAbstractGeometry> gapGeom( QgsGeometryCheckerUtils::getGeomPart( diffGeom.get(), iPart )->clone() ); // Skip the gap between features and boundingbox if ( gapGeom->boundingBox() == envelope->boundingBox() ) { continue; } // Skip gaps above threshold if ( ( mGapThresholdMapUnits > 0 && gapGeom->area() > mGapThresholdMapUnits ) || gapGeom->area() < mContext->reducedTolerance ) { continue; } QgsRectangle gapAreaBBox = gapGeom->boundingBox(); // Get neighboring polygons QMap<QString, QgsFeatureIds> neighboringIds; const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds.keys(), gapAreaBBox, compatibleGeometryTypes(), mContext ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { if ( QgsGeometryCheckerUtils::sharedEdgeLength( gapGeom.get(), layerFeature.geometry().constGet(), mContext->reducedTolerance ) > 0 ) { neighboringIds[layerFeature.layer()->id()].insert( layerFeature.feature().id() ); gapAreaBBox.combineExtentWith( layerFeature.geometry().constGet()->boundingBox() ); } } if ( neighboringIds.isEmpty() ) { continue; } // Add error double area = gapGeom->area(); errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom.release() ), neighboringIds, area, gapAreaBBox ) ); } }
void QgsGeometryGapCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const { if ( progressCounter ) progressCounter->fetchAndAddRelaxed( 1 ); QVector<QgsAbstractGeometry *> geomList; QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, 0, true ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { geomList.append( layerFeature.geometry()->clone() ); } if ( geomList.isEmpty() ) { return; } std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance ); // Create union of geometry QString errMsg; QgsAbstractGeometry *unionGeom = geomEngine->combine( geomList, &errMsg ); qDeleteAll( geomList ); if ( !unionGeom ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); return; } // Get envelope of union geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom, mContext->tolerance ); QgsAbstractGeometry *envelope = geomEngine->envelope( &errMsg ); if ( !envelope ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); delete unionGeom; return; } // Buffer envelope geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope, mContext->tolerance ); QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, GEOSBUF_CAP_SQUARE, GEOSBUF_JOIN_MITRE, 4. ); //#spellok //#spellok delete envelope; envelope = bufEnvelope; // Compute difference between envelope and union to obtain gap polygons geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope, mContext->tolerance ); QgsAbstractGeometry *diffGeom = geomEngine->difference( unionGeom, &errMsg ); if ( !diffGeom ) { messages.append( tr( "Gap check: %1" ).arg( errMsg ) ); delete unionGeom; delete diffGeom; return; } // For each gap polygon which does not lie on the boundary, get neighboring polygons and add error for ( int iPart = 0, nParts = diffGeom->partCount(); iPart < nParts; ++iPart ) { QgsAbstractGeometry *gapGeom = QgsGeometryCheckerUtils::getGeomPart( diffGeom, iPart )->clone(); // Skip the gap between features and boundingbox if ( gapGeom->boundingBox() == envelope->boundingBox() ) { continue; } // Skip gaps above threshold if ( gapGeom->area() > mThresholdMapUnits || gapGeom->area() < mContext->reducedTolerance ) { continue; } QgsRectangle gapAreaBBox = gapGeom->boundingBox(); // Get neighboring polygons QMap<QString, QgsFeatureIds> neighboringIds; QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds.keys(), gapAreaBBox, mCompatibleGeometryTypes ); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { if ( QgsGeometryCheckerUtils::sharedEdgeLength( gapGeom, layerFeature.geometry(), mContext->reducedTolerance ) > 0 ) { neighboringIds[layerFeature.layer().id()].insert( layerFeature.feature().id() ); gapAreaBBox.combineExtentWith( layerFeature.geometry()->boundingBox() ); } } if ( neighboringIds.isEmpty() ) { delete gapGeom; continue; } // Add error errors.append( new QgsGeometryGapCheckError( this, "", gapGeom, neighboringIds, gapGeom->area(), gapAreaBBox ) ); } delete unionGeom; delete envelope; delete diffGeom; }