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 QgsGeometryDuplicateNodesCheck::collectErrors( QList<QgsGeometryCheckError*>& errors, QStringList &/*messages*/, QAtomicInt* progressCounter , const QgsFeatureIds &ids ) const { const QgsFeatureIds& featureIds = ids.isEmpty() ? mFeaturePool->getFeatureIds() : ids; Q_FOREACH ( QgsFeatureId featureid, featureIds ) { if ( progressCounter ) progressCounter->fetchAndAddRelaxed( 1 ); QgsFeature feature; if ( !mFeaturePool->get( featureid, feature ) ) { continue; } QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry* geom = featureGeom.geometry(); 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++ ) { QgsPointV2 pi = geom->vertexAt( QgsVertexId( iPart, iRing, iVert ) ); QgsPointV2 pj = geom->vertexAt( QgsVertexId( iPart, iRing, jVert ) ); if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) < QgsGeometryCheckPrecision::tolerance() * QgsGeometryCheckPrecision::tolerance() ) { errors.append( new QgsGeometryCheckError( this, featureid, pj, QgsVertexId( iPart, iRing, jVert ) ) ); } } } } } }
int QgsCurvePolygon::vertexNumberFromVertexId( QgsVertexId id ) const { if ( id.part != 0 ) return -1; if ( id.ring < 0 || id.ring >= ringCount() ) return -1; int number = 0; if ( id.ring == 0 && mExteriorRing ) { return mExteriorRing->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) ); } else { number += mExteriorRing->numPoints(); } for ( int i = 0; i < mInteriorRings.count(); ++i ) { if ( id.ring == i + 1 ) { int partNumber = mInteriorRings.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) ); if ( partNumber == -1 ) return -1; return number + partNumber; } else { number += mInteriorRings.at( i )->numPoints(); } } return -1; // should not happen }
bool QgsCurvePolygon::insertVertex( QgsVertexId vId, const QgsPoint &vertex ) { if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() ) { return false; } QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 ); int n = ring->numPoints(); bool success = ring->insertVertex( QgsVertexId( 0, 0, vId.vertex ), vertex ); if ( !success ) { return false; } // If first or last vertex is inserted, re-sync the last/first vertex if ( vId.vertex == 0 ) ring->moveVertex( QgsVertexId( 0, 0, n ), vertex ); else if ( vId.vertex == n ) ring->moveVertex( QgsVertexId( 0, 0, 0 ), vertex ); clearCache(); return true; }
bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues ) { bool result = false; const QVector< QgsCurve * > curves = mCurves; int i = 0; QgsPoint lastEnd; for ( QgsCurve *curve : curves ) { result = result || curve->removeDuplicateNodes( epsilon, useZValues ); if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) ) { // empty curve, remove it delete mCurves.takeAt( i ); result = true; } else { // ensure this line starts exactly where previous line ended if ( i > 0 ) { curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd ); } lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) ); } i++; } return result; }
void QgsTriangle::setExteriorRing( QgsCurve *ring ) { if ( !ring ) { return; } if ( ring->hasCurvedSegments() ) { //need to segmentize ring as polygon does not support curves QgsCurve *line = ring->segmentize(); delete ring; ring = line; } if ( ( ring->numPoints() > 4 ) || ( ring->numPoints() < 3 ) ) { delete ring; return; } else if ( ring->numPoints() == 4 ) { if ( !ring->isClosed() ) { delete ring; return; } } else if ( ring->numPoints() == 3 ) { if ( ring->isClosed() ) { delete ring; return; } QgsLineString *lineString = static_cast< QgsLineString *>( ring ); if ( !lineString->isClosed() ) { lineString->close(); } ring = lineString; } if ( !validateGeom( ring->vertexAt( QgsVertexId( 0, 0, 0 ) ), ring->vertexAt( QgsVertexId( 0, 0, 1 ) ), ring->vertexAt( QgsVertexId( 0, 0, 2 ) ) ) ) { delete ring; return; } mExteriorRing.reset( ring ); //set proper wkb type setZMTypeFromSubGeometry( ring, QgsWkbTypes::Triangle ); clearCache(); }
void QgsGeometryUtils::adjacentVertices( const QgsAbstractGeometryV2& geom, const QgsVertexId& atVertex, QgsVertexId& beforeVertex, QgsVertexId& afterVertex ) { bool polygonType = ( geom.dimension() == 2 ); QList< QList< QList< QgsPointV2 > > > coords; geom.coordinateSequence( coords ); //get feature if ( coords.size() <= atVertex.part ) { return; //error, no such feature } const QList< QList< QgsPointV2 > >& part = coords.at( atVertex.part ); //get ring if ( part.size() <= atVertex.ring ) { return; //error, no such ring } const QList< QgsPointV2 >& ring = part.at( atVertex.ring ); if ( ring.size() <= atVertex.vertex ) { return; } //vertex in the middle if ( atVertex.vertex > 0 && atVertex.vertex < ring.size() - 1 ) { beforeVertex.part = atVertex.part; beforeVertex.ring = atVertex.ring; beforeVertex.vertex = atVertex.vertex - 1; afterVertex.part = atVertex.part; afterVertex.ring = atVertex.ring; afterVertex.vertex = atVertex.vertex + 1; } else if ( atVertex.vertex == 0 ) { afterVertex.part = atVertex.part; afterVertex.ring = atVertex.ring; afterVertex.vertex = atVertex.vertex + 1; if ( polygonType && ring.size() > 3 ) { beforeVertex.part = atVertex.part; beforeVertex.ring = atVertex.ring; beforeVertex.vertex = ring.size() - 2; } else { beforeVertex = QgsVertexId(); //before vertex invalid } } else if ( atVertex.vertex == ring.size() - 1 ) { beforeVertex.part = atVertex.part; beforeVertex.ring = atVertex.ring; beforeVertex.vertex = atVertex.vertex - 1; if ( polygonType ) { afterVertex.part = atVertex.part; afterVertex.ring = atVertex.ring; afterVertex.vertex = 1; } else { afterVertex = QgsVertexId(); //after vertex invalid } } }
void QgsGeometryCollection::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const { if ( vertex.part < 0 || vertex.part >= mGeometries.count() ) { previousVertex = QgsVertexId(); nextVertex = QgsVertexId(); return; } mGeometries.at( vertex.part )->adjacentVertices( vertex, previousVertex, nextVertex ); }
void ringAdjacentVertices( const QgsCurve *curve, QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) { int n = curve->numPoints(); if ( vertex.vertex < 0 || vertex.vertex >= n ) { previousVertex = QgsVertexId(); nextVertex = QgsVertexId(); return; } if ( vertex.vertex == 0 && n < 3 ) { previousVertex = QgsVertexId(); } else if ( vertex.vertex == 0 ) { previousVertex = QgsVertexId( vertex.part, vertex.ring, n - 2 ); } else { previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 ); } if ( vertex.vertex == n - 1 && n < 3 ) { nextVertex = QgsVertexId(); } else if ( vertex.vertex == n - 1 ) { nextVertex = QgsVertexId( vertex.part, vertex.ring, 1 ); } else { nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 ); } }
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 QgsGeometryAngleCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QgsFeatureIds &ids ) const { const QgsFeatureIds &featureIds = ids.isEmpty() ? mFeaturePool->getFeatureIds() : ids; Q_FOREACH ( QgsFeatureId featureid, featureIds ) { if ( progressCounter ) progressCounter->fetchAndAddRelaxed( 1 ); QgsFeature feature; if ( !mFeaturePool->get( featureid, feature ) ) { continue; } QgsGeometry g = feature.geometry(); const QgsAbstractGeometry *geom = g.geometry(); 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 ); // Less than three points, no angles to check if ( nVerts < 3 ) { continue; } for ( int iVert = 0; iVert < nVerts; ++iVert ) { const QgsPoint &p1 = geom->vertexAt( QgsVertexId( iPart, iRing, ( iVert - 1 + nVerts ) % nVerts ) ); const QgsPoint &p2 = geom->vertexAt( QgsVertexId( iPart, iRing, iVert ) ); const QgsPoint &p3 = geom->vertexAt( QgsVertexId( iPart, iRing, ( iVert + 1 ) % nVerts ) ); QgsVector v21, v23; try { v21 = QgsVector( p1.x() - p2.x(), p1.y() - p2.y() ).normalized(); v23 = QgsVector( p3.x() - p2.x(), p3.y() - p2.y() ).normalized(); } catch ( const QgsException & ) { // Zero length vectors continue; } double angle = std::acos( v21 * v23 ) / M_PI * 180.0; if ( angle < mMinAngle ) { errors.append( new QgsGeometryCheckError( this, featureid, p2, QgsVertexId( iPart, iRing, iVert ), angle ) ); } } } } } }
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 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 QgsGeometryAreaCheck::collectErrors( QList<QgsGeometryCheckError*>& errors, QStringList &/*messages*/, QAtomicInt* progressCounter , const QgsFeatureIds &ids ) const { const QgsFeatureIds& featureIds = ids.isEmpty() ? mFeaturePool->getFeatureIds() : ids; foreach ( const QgsFeatureId& featureid, featureIds ) { if ( progressCounter ) progressCounter->fetchAndAddRelaxed( 1 ); QgsFeature feature; if ( !mFeaturePool->get( featureid, feature ) ) { continue; } QgsAbstractGeometryV2* geom = feature.geometry()->geometry(); if ( dynamic_cast<QgsGeometryCollectionV2*>( geom ) ) { QgsGeometryCollectionV2* multiGeom = static_cast<QgsGeometryCollectionV2*>( geom ); for ( int i = 0, n = multiGeom->numGeometries(); i < n; ++i ) { double value; if ( checkThreshold( multiGeom->geometryN( i ), value ) ) { errors.append( new QgsGeometryCheckError( this, featureid, multiGeom->geometryN( i )->centroid(), QgsVertexId( i ), value, QgsGeometryCheckError::ValueArea ) ); } } } else { double value; if ( checkThreshold( geom, value ) ) { errors.append( new QgsGeometryCheckError( this, featureid, geom->centroid(), QgsVertexId( 0 ), value, QgsGeometryCheckError::ValueArea ) ); } } } }
bool QgsTriangle::moveVertex( QgsVertexId vId, const QgsPoint &newPos ) { if ( !mExteriorRing || vId.part != 0 || vId.ring != 0 || vId.vertex < 0 || vId.vertex > 4 ) { return false; } if ( vId.vertex == 4 ) { vId.vertex = 0; } QgsPoint p1( vId.vertex == 0 ? newPos : vertexAt( 0 ) ); QgsPoint p2( vId.vertex == 1 ? newPos : vertexAt( 1 ) ); QgsPoint p3( vId.vertex == 2 ? newPos : vertexAt( 2 ) ); if ( !validateGeom( p1, p2, p3 ) ) { return false; } int n = mExteriorRing->numPoints(); bool success = mExteriorRing->moveVertex( vId, newPos ); if ( success ) { // If first or last vertex is moved, also move the last/first vertex if ( vId.vertex == 0 ) mExteriorRing->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos ); clearCache(); } return success; }
bool QgsCurvePolygon::removeDuplicateNodes( double epsilon, bool useZValues ) { bool result = false; auto cleanRing = [epsilon, useZValues ]( QgsCurve * ring )->bool { if ( ring->numPoints() <= 4 ) return false; if ( ring->removeDuplicateNodes( epsilon, useZValues ) ) { QgsPoint startPoint; QgsVertexId::VertexType type; ring->pointAt( 0, startPoint, type ); // ensure ring is properly closed - if we removed the final node, it may no longer be properly closed ring->moveVertex( QgsVertexId( -1, -1, ring->numPoints() - 1 ), startPoint ); return true; } return false; }; if ( mExteriorRing ) { result = cleanRing( mExteriorRing.get() ); } for ( QgsCurve *ring : qgis::as_const( mInteriorRings ) ) { result = result || cleanRing( ring ); } return result; }
double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, bool *leftOf, double epsilon ) const { double sqrDist = std::numeric_limits<double>::max(); double testDist = 0; double segmentPtX, segmentPtY; int size = mX.size(); if ( size == 0 || size == 1 ) { vertexAfter = QgsVertexId( 0, 0, 0 ); return -1; } for ( int i = 1; i < size; ++i ) { double prevX = mX.at( i - 1 ); double prevY = mY.at( i - 1 ); double currentX = mX.at( i ); double currentY = mY.at( i ); testDist = QgsGeometryUtils::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon ); if ( testDist < sqrDist ) { sqrDist = testDist; segmentPt.setX( segmentPtX ); segmentPt.setY( segmentPtY ); if ( leftOf ) { *leftOf = ( QgsGeometryUtils::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY ) < 0 ); } vertexAfter.part = 0; vertexAfter.ring = 0; vertexAfter.vertex = i; } } return sqrDist; }
int QgsGeometryCollection::vertexNumberFromVertexId( QgsVertexId id ) const { if ( id.part < 0 || id.part >= mGeometries.count() ) return -1; int number = 0; int part = 0; for ( QgsAbstractGeometry *geometry : mGeometries ) { if ( part == id.part ) { int partNumber = geometry->vertexNumberFromVertexId( QgsVertexId( 0, id.ring, id.vertex ) ); if ( partNumber == -1 ) return -1; return number + partNumber; } else { number += geometry->nCoordinates(); } part++; } return -1; // should not happen }
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 QgsGeometryCheck::replaceFeatureGeometryPart( QgsFeature& feature, int partIdx, QgsAbstractGeometryV2* newPartGeom, Changes& changes ) const { QgsAbstractGeometryV2* geom = feature.geometry()->geometry(); if ( dynamic_cast<QgsGeometryCollectionV2*>( geom ) ) { QgsGeometryCollectionV2* GeomCollection = static_cast<QgsGeometryCollectionV2*>( geom ); GeomCollection->removeGeometry( partIdx ); GeomCollection->addGeometry( newPartGeom ); changes[feature.id()].append( Change( ChangeFeature, ChangeRemoved, QgsVertexId( partIdx ) ) ); changes[feature.id()].append( Change( ChangeFeature, ChangeAdded, QgsVertexId( GeomCollection->partCount() - 1 ) ) ); } else { feature.setGeometry( new QgsGeometry( newPartGeom ) ); changes[feature.id()].append( Change( ChangeFeature, ChangeChanged ) ); } mFeaturePool->updateFeature( feature ); }
void QgsCurvePolygon::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const { if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() ) { previousVertex = QgsVertexId(); nextVertex = QgsVertexId(); return; } if ( vertex.ring == 0 ) { ringAdjacentVertices( mExteriorRing.get(), vertex, previousVertex, nextVertex ); } else { ringAdjacentVertices( mInteriorRings.at( vertex.ring - 1 ), vertex, previousVertex, nextVertex ); } }
bool QgsCurvePolygon::deleteVertex( QgsVertexId vId ) { if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() ) { return false; } QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 ); int n = ring->numPoints(); if ( n <= 4 ) { //no points will be left in ring, so remove whole ring if ( vId.ring == 0 ) { mExteriorRing.reset(); if ( !mInteriorRings.isEmpty() ) { mExteriorRing.reset( mInteriorRings.takeFirst() ); } } else { removeInteriorRing( vId.ring - 1 ); } clearCache(); return true; } bool success = ring->deleteVertex( vId ); if ( success ) { // If first or last vertex is removed, re-sync the last/first vertex // Do not use "n - 2", but "ring->numPoints() - 1" as more than one vertex // may have been deleted (e.g. with CircularString) if ( vId.vertex == 0 ) ring->moveVertex( QgsVertexId( 0, 0, ring->numPoints() - 1 ), ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) ); else if ( vId.vertex == n - 1 ) ring->moveVertex( QgsVertexId( 0, 0, 0 ), ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) ); clearCache(); } return success; }
void TestQgsGeometryUtils::testAdjacentVertices() { // test polygon - should wrap around! QgsLineString* closedRing1 = new QgsLineString(); closedRing1->setPoints( QList<QgsPointV2>() << QgsPointV2( 1, 1 ) << QgsPointV2( 1, 2 ) << QgsPointV2( 2, 2 ) << QgsPointV2( 2, 1 ) << QgsPointV2( 1, 1 ) ); QgsPolygonV2 polygon1; polygon1.setExteriorRing( closedRing1 ); QgsVertexId previous; QgsVertexId next; QgsGeometryUtils::adjacentVertices( polygon1, QgsVertexId( 0, 0, 0 ), previous, next ); QCOMPARE( previous, QgsVertexId( 0, 0, 3 ) ); QCOMPARE( next, QgsVertexId( 0, 0, 1 ) ); // test point - both vertices should be invalid QgsPointV2 point( 1, 2 ); QgsGeometryUtils::adjacentVertices( point, QgsVertexId( 0, 0, 0 ), previous, next ); QVERIFY( !previous.isValid() ); QVERIFY( !next.isValid() ); }
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 ); }
bool QgsCurvePolygon::moveVertex( QgsVertexId vId, const QgsPoint &newPos ) { if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() ) { return false; } QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 ); int n = ring->numPoints(); bool success = ring->moveVertex( vId, newPos ); if ( success ) { // If first or last vertex is moved, also move the last/first vertex if ( vId.vertex == 0 ) ring->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos ); else if ( vId.vertex == n - 1 ) ring->moveVertex( QgsVertexId( vId.part, vId.ring, 0 ), newPos ); clearCache(); } return success; }
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 QgsCurve::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const { int n = numPoints(); if ( vertex.vertex < 0 || vertex.vertex >= n ) { previousVertex = QgsVertexId(); nextVertex = QgsVertexId(); return; } if ( vertex.vertex == 0 ) { previousVertex = QgsVertexId(); } else { previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 ); } if ( vertex.vertex == n - 1 ) { nextVertex = QgsVertexId(); } else { nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 ); } }
void QgsGeometryDuplicateNodesCheck::fixError( QgsGeometryCheckError* error, int method, int /*mergeAttributeIndex*/, Changes &changes ) const { QgsFeature feature; if ( !mFeaturePool->get( error->featureId(), feature ) ) { error->setObsolete(); return; } QgsAbstractGeometryV2* geom = feature.geometry()->geometry(); QgsVertexId vidx = error->vidx(); // Check if point still exists if ( !vidx.isValid( geom ) ) { error->setObsolete(); return; } // Check if error still applies int nVerts = QgsGeomUtils::polyLineSize( geom, vidx.part, vidx.ring ); QgsPointV2 pi = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( vidx.vertex + nVerts - 1 ) % nVerts ) ); QgsPointV2 pj = geom->vertexAt( error->vidx() ); if ( QgsGeometryUtils::sqrDistance2D( pi, pj ) >= QgsGeometryCheckPrecision::tolerance() * QgsGeometryCheckPrecision::tolerance() ) { error->setObsolete(); return; } // Fix error if ( method == NoChange ) { error->setFixed( method ); } else if ( method == RemoveDuplicates ) { geom->deleteVertex( error->vidx() ); if ( QgsGeomUtils::polyLineSize( geom, vidx.part, vidx.ring ) < 3 ) { error->setFixFailed( tr( "Resulting geometry is degenerate" ) ); } else { mFeaturePool->updateFeature( feature ); error->setFixed( method ); changes[error->featureId()].append( Change( ChangeNode, ChangeRemoved, error->vidx() ) ); } } else { error->setFixFailed( tr( "Unknown method" ) ); } }
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 QgsGeometrySegmentLengthCheck::collectErrors( QList<QgsGeometryCheckError*>& errors, QStringList &/*messages*/, QAtomicInt* progressCounter , const QgsFeatureIds &ids ) const { const QgsFeatureIds& featureIds = ids.isEmpty() ? mFeaturePool->getFeatureIds() : ids; Q_FOREACH ( QgsFeatureId featureid, featureIds ) { if ( progressCounter ) progressCounter->fetchAndAddRelaxed( 1 ); QgsFeature feature; if ( !mFeaturePool->get( featureid, feature ) ) { continue; } QgsGeometry featureGeom = feature.geometry(); QgsAbstractGeometry* geom = featureGeom.geometry(); 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 = 0, jVert = nVerts - 1; iVert < nVerts; jVert = iVert++ ) { QgsPointV2 pi = geom->vertexAt( QgsVertexId( iPart, iRing, iVert ) ); QgsPointV2 pj = geom->vertexAt( QgsVertexId( iPart, iRing, jVert ) ); double dist = qSqrt( QgsGeometryUtils::sqrDistance2D( pi, pj ) ); if ( dist < mMinLength ) { errors.append( new QgsGeometryCheckError( this, featureid, QgsPointV2( 0.5 * ( pi.x() + pj.x() ), 0.5 * ( pi.y() + pj.y() ) ), QgsVertexId( iPart, iRing, iVert ), dist, QgsGeometryCheckError::ValueLength ) ); } } } } } }