void QgsGeometryCheck::deleteFeatureGeometryPart( const QString &layerId, QgsFeature &feature, int partIdx, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[layerId]; QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry *geom = featureGeom.get(); if ( dynamic_cast<QgsGeometryCollection *>( geom ) ) { static_cast<QgsGeometryCollection *>( geom )->removeGeometry( partIdx ); if ( static_cast<QgsGeometryCollection *>( geom )->numGeometries() == 0 ) { featurePool->deleteFeature( feature.id() ); changes[layerId][feature.id()].append( Change( ChangeFeature, ChangeRemoved ) ); } else { feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); changes[layerId][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( partIdx ) ) ); } } else { featurePool->deleteFeature( feature.id() ); changes[layerId][feature.id()].append( Change( ChangeFeature, ChangeRemoved ) ); } }
void QgsGeometryCheck::deleteFeatureGeometryRing( const QString &layerId, QgsFeature &feature, int partIdx, int ringIdx, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[layerId]; QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry *partGeom = QgsGeometryCheckerUtils::getGeomPart( featureGeom.get(), partIdx ); if ( dynamic_cast<QgsCurvePolygon *>( partGeom ) ) { // If we delete the exterior ring of a polygon, it makes no sense to keep the interiors if ( ringIdx == 0 ) { deleteFeatureGeometryPart( layerId, feature, partIdx, changes ); } else { static_cast<QgsCurvePolygon *>( partGeom )->removeInteriorRing( ringIdx - 1 ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); changes[layerId][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( partIdx, ringIdx ) ) ); } } // Other geometry types do not have rings, remove the entire part else { deleteFeatureGeometryPart( layerId, feature, partIdx, changes ); } }
void QgsGeometryHoleCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->get( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsGeometry featureGeom = feature.geometry(); const QgsAbstractGeometry *geom = featureGeom.constGet(); QgsVertexId vidx = error->vidx(); // Check if ring still exists if ( !vidx.isValid( geom ) ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == RemoveHoles ) { deleteFeatureGeometryRing( error->layerId(), feature, vidx.part, vidx.ring, changes ); error->setFixed( method ); } else { error->setFixFailed( tr( "Unknown method" ) ); } }
void QgsGeometryDuplicateNodesCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsFeaturePool *featurePool = featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->getFeature( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry *geom = featureGeom.get(); QgsVertexId vidx = error->vidx(); // Check if point still exists if ( !vidx.isValid( geom ) ) { error->setObsolete(); return; } // Check if error still applies int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, vidx.part, vidx.ring ); QgsPoint pi = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( vidx.vertex + nVerts - 1 ) % nVerts ) ); QgsPoint pj = geom->vertexAt( error->vidx() ); if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) >= mContext->tolerance ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == RemoveDuplicates ) { if ( !QgsGeometryCheckerUtils::canDeleteVertex( geom, vidx.part, vidx.ring ) ) { error->setFixFailed( tr( "Resulting geometry is degenerate" ) ); } else if ( !geom->deleteVertex( error->vidx() ) ) { error->setFixFailed( tr( "Failed to delete vertex" ) ); } else { feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); error->setFixed( method ); changes[error->layerId()][error->featureId()].append( Change( ChangeNode, ChangeRemoved, error->vidx() ) ); } } else { error->setFixFailed( tr( "Unknown method" ) ); } }
void QgsGeometryAreaCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->get( error->featureId(), feature ) ) { error->setObsolete(); return; } double layerToMapUnits = featurePool->getLayerToMapUnits(); QgsGeometry g = feature.geometry(); const QgsAbstractGeometry *geom = g.constGet(); QgsVertexId vidx = error->vidx(); // Check if polygon still exists if ( !vidx.isValid( geom ) ) { error->setObsolete(); return; } // Check if error still applies double value; if ( !checkThreshold( layerToMapUnits, QgsGeometryCheckerUtils::getGeomPart( geom, vidx.part ), value ) ) { error->setObsolete(); return; } // Fix with selected method if ( method == NoChange ) { error->setFixed( method ); } else if ( method == Delete ) { deleteFeatureGeometryPart( error->layerId(), feature, vidx.part, changes ); error->setFixed( method ); } else if ( method == MergeLongestEdge || method == MergeLargestArea || method == MergeIdenticalAttribute ) { QString errMsg; if ( mergeWithNeighbor( error->layerId(), feature, vidx.part, method, mergeAttributeIndices[error->layerId()], changes, errMsg ) ) { error->setFixed( method ); } else { error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) ); } } else { error->setFixFailed( tr( "Unknown method" ) ); } }
void QgsGeometryContainedCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsGeometryContainedCheckError *containerError = static_cast<QgsGeometryContainedCheckError *>( error ); QgsFeaturePool *featurePoolA = mContext->featurePools[ error->layerId() ]; QgsFeaturePool *featurePoolB = mContext->featurePools[ containerError->containingFeature().first ]; QgsFeature featureA; QgsFeature featureB; if ( !featurePoolA->get( error->featureId(), featureA ) || !featurePoolB->get( containerError->containingFeature().second, featureB ) ) { error->setObsolete(); return; } // Check if error still applies QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, true ); QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, true ); std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry(), mContext->tolerance ); std::unique_ptr< QgsGeometryEngine > geomEngineB = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureB.geometry(), mContext->tolerance ); if ( !( geomEngineB->contains( layerFeatureA.geometry() ) && !geomEngineA->contains( layerFeatureB.geometry() ) ) ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == Delete ) { changes[error->layerId()][featureA.id()].append( Change( ChangeFeature, ChangeRemoved ) ); featurePoolA->deleteFeature( featureA.id() ); error->setFixed( method ); } else { error->setFixFailed( tr( "Unknown method" ) ); } }
void QgsGeometryMultipartCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsFeaturePool *featurePool = featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->getFeature( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsGeometry featureGeom = feature.geometry(); const QgsAbstractGeometry *geom = featureGeom.constGet(); // Check if error still applies if ( geom->partCount() > 1 || !QgsWkbTypes::isMultiType( geom->wkbType() ) ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == ConvertToSingle ) { feature.setGeometry( QgsGeometry( QgsGeometryCheckerUtils::getGeomPart( geom, 0 )->clone() ) ); featurePool->updateFeature( feature ); error->setFixed( method ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } else if ( method == RemoveObject ) { featurePool->deleteFeature( feature.id() ); error->setFixed( method ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeRemoved ) ); } else { error->setFixFailed( tr( "Unknown method" ) ); } }
void QgsGeometryCheck::replaceFeatureGeometryPart( const QString &layerId, QgsFeature &feature, int partIdx, QgsAbstractGeometry *newPartGeom, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[layerId]; QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry *geom = featureGeom.get(); if ( QgsGeometryCollection *geomCollection = dynamic_cast< QgsGeometryCollection *>( geom ) ) { geomCollection->removeGeometry( partIdx ); geomCollection->addGeometry( newPartGeom ); changes[layerId][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( partIdx ) ) ); changes[layerId][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) ); feature.setGeometry( featureGeom ); } else { feature.setGeometry( QgsGeometry( newPartGeom ) ); changes[layerId][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } featurePool->updateFeature( feature ); }
void QgsGeometryValidationService::fixError( QgsGeometryCheckError *error, int method ) { QgsGeometryCheck::Changes changes; error->check()->fixError( mFeaturePools, error, method, QMap<QString, int>(), changes ); error->setFixed( method ); QgsFeaturePool *featurePool = mFeaturePools.value( error->layerId() ); QgsVectorLayer *layer = nullptr; if ( featurePool ) layer = featurePool->layer(); else { // Some checks don't tell us on which layer they are because they are able to do cross-layer checks. // E.g. the gap check will report in such a way for ( auto layerCheck = mLayerChecks.constBegin(); layerCheck != mLayerChecks.constEnd(); ++layerCheck ) { const QList<std::shared_ptr<QgsGeometryCheckError>> &topologyCheckErrors = layerCheck.value().topologyCheckErrors; for ( const auto &checkError : topologyCheckErrors ) { if ( checkError.get() == error ) { layer = layerCheck.key(); break; } } } } if ( layer ) { layer->triggerRepaint(); emit topologyErrorUpdated( layer, error ); } }
void QgsGeometryOverlapCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QString errMsg; QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error ); QgsFeaturePool *featurePoolA = mContext->featurePools[ overlapError->layerId() ]; QgsFeaturePool *featurePoolB = mContext->featurePools[ overlapError->overlappedFeature().first ]; QgsFeature featureA; QgsFeature featureB; if ( !featurePoolA->get( overlapError->featureId(), featureA ) || !featurePoolB->get( overlapError->overlappedFeature().second, featureB ) ) { error->setObsolete(); return; } // Check if error still applies QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, true ); QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, true ); std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry(), mContext->reducedTolerance ); if ( !geomEngineA->overlaps( layerFeatureB.geometry() ) ) { error->setObsolete(); return; } std::unique_ptr< QgsAbstractGeometry > interGeom( geomEngineA->intersection( layerFeatureB.geometry(), &errMsg ) ); if ( !interGeom ) { error->setFixFailed( tr( "Failed to compute intersection between overlapping features: %1" ).arg( errMsg ) ); return; } // Search which overlap part this error parametrizes (using fuzzy-matching of the area and centroid...) QgsAbstractGeometry *interPart = nullptr; for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart ) { QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart ); if ( std::fabs( part->area() - overlapError->value().toDouble() ) < mContext->reducedTolerance && QgsGeometryCheckerUtils::pointsFuzzyEqual( part->centroid(), overlapError->location(), mContext->reducedTolerance ) ) { interPart = part; break; } } if ( !interPart || interPart->isEmpty() ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == Subtract ) { std::unique_ptr< QgsAbstractGeometry > diff1( geomEngineA->difference( interPart, &errMsg ) ); if ( !diff1 || diff1->isEmpty() ) { diff1.reset(); } else { QgsGeometryCheckerUtils::filter1DTypes( diff1.get() ); } std::unique_ptr< QgsGeometryEngine > geomEngineB = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureB.geometry(), mContext->reducedTolerance ); std::unique_ptr< QgsAbstractGeometry > diff2( geomEngineB->difference( interPart, &errMsg ) ); if ( !diff2 || diff2->isEmpty() ) { diff2.reset(); } else { QgsGeometryCheckerUtils::filter1DTypes( diff2.get() ); } double shared1 = diff1 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff1.get(), interPart, mContext->reducedTolerance ) : 0; double shared2 = diff2 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff2.get(), interPart, mContext->reducedTolerance ) : 0; if ( !diff1 || !diff2 || shared1 == 0. || shared2 == 0. ) { error->setFixFailed( tr( "Could not find shared edges between intersection and overlapping features" ) ); } else { if ( shared1 < shared2 ) { diff1->transform( featurePoolA->getLayerToMapTransform(), QgsCoordinateTransform::ReverseTransform ); featureA.setGeometry( QgsGeometry( std::move( diff1 ) ) ); changes[error->layerId()][featureA.id()].append( Change( ChangeFeature, ChangeChanged ) ); featurePoolA->updateFeature( featureA ); } else { diff2->transform( featurePoolB->getLayerToMapTransform(), QgsCoordinateTransform::ReverseTransform ); featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) ); changes[overlapError->overlappedFeature().first][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) ); featurePoolB->updateFeature( featureB ); } error->setFixed( method ); } } else { error->setFixFailed( tr( "Unknown method" ) ); } }
bool QgsGeometryAreaCheck::mergeWithNeighbor( const QString &layerId, QgsFeature &feature, int partIdx, int method, int mergeAttributeIndex, Changes &changes, QString &errMsg ) const { QgsFeaturePool *featurePool = mContext->featurePools[ layerId ]; double maxVal = 0.; QgsFeature mergeFeature; int mergePartIdx = -1; bool matchFound = false; QgsGeometry featureGeometry = feature.geometry(); const QgsAbstractGeometry *geom = featureGeometry.constGet(); // Search for touching neighboring geometries const QgsFeatureIds intersects = featurePool->getIntersects( featureGeometry.boundingBox() ); for ( QgsFeatureId testId : intersects ) { QgsFeature testFeature; if ( !featurePool->get( testId, testFeature ) ) { continue; } QgsGeometry testFeatureGeom = testFeature.geometry(); const QgsAbstractGeometry *testGeom = testFeatureGeom.constGet(); for ( int testPartIdx = 0, nTestParts = testGeom->partCount(); testPartIdx < nTestParts; ++testPartIdx ) { if ( testId == feature.id() && testPartIdx == partIdx ) { continue; } double len = QgsGeometryCheckerUtils::sharedEdgeLength( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), QgsGeometryCheckerUtils::getGeomPart( testGeom, testPartIdx ), mContext->reducedTolerance ); if ( len > 0. ) { if ( method == MergeLongestEdge || method == MergeLargestArea ) { double val; if ( method == MergeLongestEdge ) { val = len; } else { if ( dynamic_cast<const QgsGeometryCollection *>( testGeom ) ) val = static_cast<const QgsGeometryCollection *>( testGeom )->geometryN( testPartIdx )->area(); else val = testGeom->area(); } if ( val > maxVal ) { maxVal = val; mergeFeature = testFeature; mergePartIdx = testPartIdx; } } else if ( method == MergeIdenticalAttribute ) { if ( testFeature.attribute( mergeAttributeIndex ) == feature.attribute( mergeAttributeIndex ) ) { mergeFeature = testFeature; mergePartIdx = testPartIdx; matchFound = true; break; } } } } if ( matchFound ) { break; } } if ( !matchFound && maxVal == 0. ) { return method == MergeIdenticalAttribute; } // Merge geometries QgsGeometry mergeFeatureGeom = mergeFeature.geometry(); const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet(); std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometryCheckerUtils::createGeomEngine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), mContext->reducedTolerance ) ); QgsAbstractGeometry *combinedGeom = geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), &errMsg ); if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) ) { return false; } // Replace polygon in merge geometry if ( mergeFeature.id() == feature.id() && mergePartIdx > partIdx ) { --mergePartIdx; } replaceFeatureGeometryPart( layerId, mergeFeature, mergePartIdx, combinedGeom, changes ); // Remove polygon from source geometry deleteFeatureGeometryPart( layerId, feature, partIdx, changes ); return true; }
void QgsGeometryTypeCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->get( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsGeometry featureGeom = feature.geometry(); const QgsAbstractGeometry *geom = featureGeom.constGet(); // Check if error still applies QgsWkbTypes::Type type = QgsWkbTypes::flatType( geom->wkbType() ); if ( ( mAllowedTypes & ( 1 << type ) ) != 0 ) { error->setObsolete(); return; } // Fix with selected method if ( method == NoChange ) { error->setFixed( method ); } else if ( method == Convert ) { // Check if corresponding single type is allowed if ( QgsWkbTypes::isMultiType( type ) && ( ( 1 << QgsWkbTypes::singleType( type ) ) & mAllowedTypes ) != 0 ) { // Explode multi-type feature into single-type features for ( int iPart = 1, nParts = geom->partCount(); iPart < nParts; ++iPart ) { QgsFeature newFeature; newFeature.setAttributes( feature.attributes() ); newFeature.setGeometry( QgsGeometry( QgsGeometryCheckerUtils::getGeomPart( geom, iPart )->clone() ) ); featurePool->addFeature( newFeature ); changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) ); } // Recycle feature for part 0 feature.setGeometry( QgsGeometry( QgsGeometryCheckerUtils::getGeomPart( geom, 0 )->clone() ) ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } // Check if corresponding multi type is allowed else if ( QgsWkbTypes::isSingleType( type ) && ( ( 1 << QgsWkbTypes::multiType( type ) ) & mAllowedTypes ) != 0 ) { QgsGeometryCollection *geomCollection = nullptr; switch ( QgsWkbTypes::multiType( type ) ) { case QgsWkbTypes::MultiPoint: { geomCollection = new QgsMultiPoint(); break; } case QgsWkbTypes::MultiLineString: { geomCollection = new QgsMultiLineString(); break; } case QgsWkbTypes::MultiPolygon: { geomCollection = new QgsMultiPolygon(); break; } case QgsWkbTypes::MultiCurve: { geomCollection = new QgsMultiCurve(); break; } case QgsWkbTypes::MultiSurface: { geomCollection = new QgsMultiSurface(); break; } default: break; } if ( !geomCollection ) { error->setFixFailed( tr( "Unknown geometry type" ) ); } else { geomCollection->addGeometry( geom->clone() ); feature.setGeometry( QgsGeometry( geomCollection ) ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } } // Delete feature else { featurePool->deleteFeature( feature.id() ); changes[error->layerId()][error->featureId()].append( Change( ChangeFeature, ChangeRemoved ) ); } error->setFixed( method ); } else if ( method == Delete ) { featurePool->deleteFeature( feature.id() ); error->setFixed( method ); changes[error->layerId()][error->featureId()].append( Change( ChangeFeature, ChangeRemoved ) ); } else { error->setFixFailed( tr( "Unknown method" ) ); } }
bool QgsGeometryGapCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryGapCheckError *err, Changes &changes, QString &errMsg ) const { double maxVal = 0.; QString mergeLayerId; QgsFeature mergeFeature; int mergePartIdx = -1; const QgsGeometry geometry = err->geometry(); const QgsAbstractGeometry *errGeometry = QgsGeometryCheckerUtils::getGeomPart( geometry.constGet(), 0 ); const auto layerIds = err->neighbors().keys(); // Search for touching neighboring geometries for ( const QString &layerId : layerIds ) { QgsFeaturePool *featurePool = featurePools.value( layerId ); std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() ); QgsCoordinateTransform ct( featurePool->crs(), mContext->mapCrs, mContext->transformContext ); errLayerGeom->transform( ct, QgsCoordinateTransform::ReverseTransform ); const auto featureIds = err->neighbors().value( layerId ); for ( QgsFeatureId testId : featureIds ) { QgsFeature testFeature; if ( !featurePool->getFeature( testId, testFeature ) ) { continue; } QgsGeometry featureGeom = testFeature.geometry(); const QgsAbstractGeometry *testGeom = featureGeom.constGet(); for ( int iPart = 0, nParts = testGeom->partCount(); iPart < nParts; ++iPart ) { double len = QgsGeometryCheckerUtils::sharedEdgeLength( errLayerGeom.get(), QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart ), mContext->reducedTolerance ); if ( len > maxVal ) { maxVal = len; mergeFeature = testFeature; mergePartIdx = iPart; mergeLayerId = layerId; } } } } if ( maxVal == 0. ) { return false; } // Merge geometries QgsFeaturePool *featurePool = featurePools[ mergeLayerId ]; std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() ); QgsCoordinateTransform ct( featurePool->crs(), mContext->mapCrs, mContext->transformContext ); errLayerGeom->transform( ct, QgsCoordinateTransform::ReverseTransform ); QgsGeometry mergeFeatureGeom = mergeFeature.geometry(); const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet(); std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( errLayerGeom.get(), mContext->reducedTolerance ); std::unique_ptr<QgsAbstractGeometry> combinedGeom( geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), &errMsg ) ); if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) ) { return false; } // Add merged polygon to destination geometry replaceFeatureGeometryPart( featurePools, mergeLayerId, mergeFeature, mergePartIdx, combinedGeom.release(), changes ); return true; }
void QgsGeometrySelfIntersectionCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const { QgsFeaturePool *featurePool = mContext->featurePools[ error->layerId() ]; QgsFeature feature; if ( !featurePool->getFeature( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry *geom = featureGeom.get(); QgsVertexId vidx = error->vidx(); // Check if ring still exists if ( !vidx.isValid( geom ) ) { error->setObsolete(); return; } const QgsGeometryUtils::SelfIntersection &inter = static_cast<QgsGeometrySelfIntersectionCheckError *>( error )->intersection(); // Check if error still applies bool ringIsClosed = false; int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, vidx.part, vidx.ring, &ringIsClosed ); if ( nVerts == 0 || inter.segment1 >= nVerts || inter.segment2 >= nVerts ) { error->setObsolete(); return; } QgsPoint p1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment1 ) ); QgsPoint q1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment2 ) ); QgsPoint p2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment1 + 1 ) % nVerts ) ); QgsPoint q2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment2 + 1 ) % nVerts ) ); QgsPoint s; bool intersection = false; if ( !QgsGeometryUtils::segmentIntersection( p1, p2, q1, q2, s, intersection, mContext->tolerance ) ) { error->setObsolete(); return; } // Fix with selected method if ( method == NoChange ) { error->setFixed( method ); } else if ( method == ToMultiObject || method == ToSingleObjects ) { // Extract rings QgsPointSequence ring1, ring2; bool ring1EndsWithS = false; bool ring2EndsWithS = false; for ( int i = 0; i < nVerts; ++i ) { if ( i <= inter.segment1 || i >= inter.segment2 + 1 ) { ring1.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) ); ring1EndsWithS = false; if ( i == inter.segment2 + 1 ) { ring2.append( s ); ring2EndsWithS = true; } } else { ring2.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) ); ring2EndsWithS = true; if ( i == inter.segment1 + 1 ) { ring1.append( s ); ring1EndsWithS = false; } } } if ( nVerts == inter.segment2 + 1 ) { ring2.append( s ); ring2EndsWithS = true; } if ( ringIsClosed || ring1EndsWithS ) ring1.append( ring1.front() ); // Ensure ring is closed if ( ringIsClosed || ring2EndsWithS ) ring2.append( ring2.front() ); // Ensure ring is closed if ( ring1.size() < 3 + ( ringIsClosed || ring1EndsWithS ) || ring2.size() < 3 + ( ringIsClosed || ring2EndsWithS ) ) { error->setFixFailed( tr( "Resulting geometry is degenerate" ) ); return; } QgsLineString *ringGeom1 = new QgsLineString(); ringGeom1->setPoints( ring1 ); QgsLineString *ringGeom2 = new QgsLineString(); ringGeom2->setPoints( ring2 ); QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( geom, vidx.part ); // If is a polygon... if ( dynamic_cast<QgsCurvePolygon *>( part ) ) { QgsCurvePolygon *poly = static_cast<QgsCurvePolygon *>( part ); // If self-intersecting ring is an interior ring, create separate holes if ( vidx.ring > 0 ) { poly->removeInteriorRing( vidx.ring ); poly->addInteriorRing( ringGeom1 ); poly->addInteriorRing( ringGeom2 ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, vidx ) ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 2 ) ) ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 1 ) ) ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); } else { // If ring is exterior, build two polygons, and reassign interiors as necessary poly->setExteriorRing( ringGeom1 ); // If original feature was a linear polygon, also create the new part as a linear polygon QgsCurvePolygon *poly2 = dynamic_cast<QgsPolygon *>( part ) ? new QgsPolygon() : new QgsCurvePolygon(); poly2->setExteriorRing( ringGeom2 ); // Reassing interiors as necessary std::unique_ptr< QgsGeometryEngine > geomEnginePoly1 = QgsGeometryCheckerUtils::createGeomEngine( poly, mContext->tolerance ); std::unique_ptr< QgsGeometryEngine > geomEnginePoly2 = QgsGeometryCheckerUtils::createGeomEngine( poly2, mContext->tolerance ); for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i ) { if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) ) { if ( geomEnginePoly2->contains( poly->interiorRing( i ) ) ) { poly2->addInteriorRing( static_cast<QgsCurve *>( poly->interiorRing( i )->clone() ) ); // No point in adding ChangeAdded changes, since the entire poly2 is added anyways later on } poly->removeInteriorRing( i ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( vidx.part, 1 + i ) ) ); } } if ( method == ToMultiObject ) { // If is already a geometry collection, just add the new polygon. if ( dynamic_cast<QgsGeometryCollection *>( geom ) ) { static_cast<QgsGeometryCollection *>( geom )->addGeometry( poly2 ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geom->partCount() - 1 ) ) ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); } // Otherwise, create multipolygon else { QgsMultiPolygon *multiPoly = new QgsMultiPolygon(); multiPoly->addGeometry( poly->clone() ); multiPoly->addGeometry( poly2 ); feature.setGeometry( QgsGeometry( multiPoly ) ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } } else // if ( method == ToSingleObjects ) { QgsFeature newFeature; newFeature.setAttributes( feature.attributes() ); newFeature.setGeometry( QgsGeometry( poly2 ) ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); featurePool->addFeature( newFeature ); changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) ); changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) ); } } } else if ( dynamic_cast<QgsCurve *>( part ) ) { if ( method == ToMultiObject ) { if ( dynamic_cast<QgsGeometryCollection *>( geom ) ) { QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom ); geomCollection->removeGeometry( vidx.part ); geomCollection->addGeometry( ringGeom1 ); geomCollection->addGeometry( ringGeom2 ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 2 ) ) ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) ); } else { QgsMultiCurve *geomCollection = new QgsMultiLineString(); geomCollection->addGeometry( ringGeom1 ); geomCollection->addGeometry( ringGeom2 ); feature.setGeometry( QgsGeometry( geomCollection ) ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } } else // if(method == ToSingleObjects) { if ( dynamic_cast<QgsGeometryCollection *>( geom ) ) { QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom ); geomCollection->removeGeometry( vidx.part ); geomCollection->addGeometry( ringGeom1 ); feature.setGeometry( featureGeom ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) ); changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) ); } else { feature.setGeometry( QgsGeometry( ringGeom1 ) ); featurePool->updateFeature( feature ); changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged, QgsVertexId( vidx.part ) ) ); } QgsFeature newFeature; newFeature.setAttributes( feature.attributes() ); newFeature.setGeometry( QgsGeometry( ringGeom2 ) ); featurePool->addFeature( newFeature ); changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) ); } } else { delete ringGeom1; delete ringGeom2; } error->setFixed( method ); } else { error->setFixFailed( tr( "Unknown method" ) ); } }
bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, bool triggerRepaint ) { mMessages.clear(); if ( error->status() >= QgsGeometryCheckError::StatusFixed ) { return true; } #if 0 QTextStream( stdout ) << "Fixing " << error->description() << ": " << error->layerId() << ":" << error->featureId() << " @[" << error->vidx().part << ", " << error->vidx().ring << ", " << error->vidx().vertex << "](" << error->location().x() << ", " << error->location().y() << ") = " << error->value().toString() << endl; #endif QgsGeometryCheck::Changes changes; QgsRectangle recheckArea = error->affectedAreaBBox(); error->check()->fixError( error, method, mMergeAttributeIndices, changes ); #if 0 QTextStream( stdout ) << " * Status: " << error->resolutionMessage() << endl; static QVector<QString> strChangeWhat = { "ChangeFeature", "ChangePart", "ChangeRing", "ChangeNode" }; static QVector<QString> strChangeType = { "ChangeAdded", "ChangeRemoved", "ChangeChanged" }; for ( const QString &layerId : changes.keys() ) { for ( const QgsFeatureId &fid : changes[layerId].keys() ) { for ( const QgsGeometryCheck::Change &change : changes[layerId][fid] ) { QTextStream( stdout ) << " * Change: " << layerId << ":" << fid << " :: " << strChangeWhat[change.what] << ":" << strChangeType[change.type] << ":(" << change.vidx.part << "," << change.vidx.ring << "," << change.vidx.vertex << ")" << endl; } } } #endif emit errorUpdated( error, true ); if ( error->status() != QgsGeometryCheckError::StatusFixed ) { return false; } // If nothing was changed, stop here if ( changes.isEmpty() ) { return true; } // Determine what to recheck // - Collect all features which were changed, get affected area QMap<QString, QSet<QgsFeatureId>> recheckFeatures; for ( auto it = changes.constBegin(); it != changes.constEnd(); ++it ) { const QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> &layerChanges = it.value(); QgsFeaturePool *featurePool = mContext->featurePools[it.key()]; QgsCoordinateTransform t( featurePool->getLayer()->crs(), mContext->mapCrs, QgsProject::instance() ); for ( auto layerChangeIt = layerChanges.constBegin(); layerChangeIt != layerChanges.constEnd(); ++layerChangeIt ) { bool removed = false; for ( const QgsGeometryCheck::Change &change : layerChangeIt.value() ) { if ( change.what == QgsGeometryCheck::ChangeFeature && change.type == QgsGeometryCheck::ChangeRemoved ) { removed = true; break; } } if ( !removed ) { QgsFeature f; if ( featurePool->get( layerChangeIt.key(), f ) ) { recheckFeatures[it.key()].insert( layerChangeIt.key() ); recheckArea.combineExtentWith( t.transformBoundingBox( f.geometry().boundingBox() ) ); } } } } // - Determine extent to recheck for gaps for ( QgsGeometryCheckError *err : qgis::as_const( mCheckErrors ) ) { if ( err->check()->getCheckType() == QgsGeometryCheck::LayerCheck ) { if ( err->affectedAreaBBox().intersects( recheckArea ) ) { recheckArea.combineExtentWith( err->affectedAreaBBox() ); } } } recheckArea.grow( 10 * mContext->tolerance ); QMap<QString, QgsFeatureIds> recheckAreaFeatures; for ( const QString &layerId : mContext->featurePools.keys() ) { QgsFeaturePool *featurePool = mContext->featurePools[layerId]; QgsCoordinateTransform t( mContext->mapCrs, featurePool->getLayer()->crs(), QgsProject::instance() ); recheckAreaFeatures[layerId] = featurePool->getIntersects( t.transform( recheckArea ) ); } // Recheck feature / changed area to detect new errors QList<QgsGeometryCheckError *> recheckErrors; for ( const QgsGeometryCheck *check : qgis::as_const( mChecks ) ) { if ( check->getCheckType() == QgsGeometryCheck::LayerCheck ) { if ( !recheckAreaFeatures.isEmpty() ) { check->collectErrors( recheckErrors, mMessages, nullptr, recheckAreaFeatures ); } } else { if ( !recheckFeatures.isEmpty() ) { check->collectErrors( recheckErrors, mMessages, nullptr, recheckFeatures ); } } } // Go through error list, update other errors of the checked feature for ( QgsGeometryCheckError *err : qgis::as_const( mCheckErrors ) ) { if ( err == error || err->status() == QgsGeometryCheckError::StatusObsolete ) { continue; } QgsGeometryCheckError::Status oldStatus = err->status(); bool handled = err->handleChanges( changes ); // Check if this error now matches one found when rechecking the feature/area QgsGeometryCheckError *matchErr = nullptr; int nMatch = 0; for ( QgsGeometryCheckError *recheckErr : qgis::as_const( recheckErrors ) ) { if ( recheckErr->isEqual( err ) || recheckErr->closeMatch( err ) ) { ++nMatch; matchErr = recheckErr; } } // If just one close match was found, take it if ( nMatch == 1 && matchErr ) { err->update( matchErr ); emit errorUpdated( err, err->status() != oldStatus ); recheckErrors.removeAll( matchErr ); delete matchErr; continue; } // If no match is found and the error is not fixed or obsolete, set it to obsolete if... if ( err->status() < QgsGeometryCheckError::StatusFixed && ( // changes weren't handled !handled || // or if it is a FeatureNodeCheck or FeatureCheck error whose feature was rechecked ( err->check()->getCheckType() <= QgsGeometryCheck::FeatureCheck && recheckFeatures[err->layerId()].contains( err->featureId() ) ) || // or if it is a LayerCheck error within the rechecked area ( err->check()->getCheckType() == QgsGeometryCheck::LayerCheck && recheckArea.contains( err->affectedAreaBBox() ) ) ) ) { err->setObsolete(); emit errorUpdated( err, err->status() != oldStatus ); } } // Add new errors for ( QgsGeometryCheckError *recheckErr : qgis::as_const( recheckErrors ) ) { emit errorAdded( recheckErr ); mCheckErrors.append( recheckErr ); } if ( triggerRepaint ) { for ( const QString &layerId : changes.keys() ) { mContext->featurePools[layerId]->getLayer()->triggerRepaint(); } } return true; }