QgsRectangle QgsCircularString::calculateBoundingBox() const { QgsRectangle bbox; int nPoints = numPoints(); for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 ) { if ( i == 0 ) { bbox = segmentBoundingBox( QgsPointV2( mX[i], mY[i] ), QgsPointV2( mX[i + 1], mY[i + 1] ), QgsPointV2( mX[i + 2], mY[i + 2] ) ); } else { QgsRectangle segmentBox = segmentBoundingBox( QgsPointV2( mX[i], mY[i] ), QgsPointV2( mX[i + 1], mY[i + 1] ), QgsPointV2( mX[i + 2], mY[i + 2] ) ); bbox.combineExtentWith( segmentBox ); } } if ( nPoints > 0 && nPoints % 2 == 0 ) { if ( nPoints == 2 ) { bbox.combineExtentWith( mX[ 0 ], mY[ 0 ] ); } bbox.combineExtentWith( mX[ nPoints - 1 ], mY[ nPoints - 1 ] ); } return bbox; }
QgsRectangle QgsProcessingUtils::combineLayerExtents( const QList<QgsMapLayer *> &layers, const QgsCoordinateReferenceSystem &crs ) { QgsRectangle extent; for ( const QgsMapLayer *layer : layers ) { if ( !layer ) continue; if ( crs.isValid() ) { //transform layer extent to target CRS Q_NOWARN_DEPRECATED_PUSH QgsCoordinateTransform ct( layer->crs(), crs ); Q_NOWARN_DEPRECATED_POP try { QgsRectangle reprojExtent = ct.transformBoundingBox( layer->extent() ); extent.combineExtentWith( reprojExtent ); } catch ( QgsCsException & ) { // can't reproject... what to do here? hmmm? // let's ignore this layer for now, but maybe we should just use the original extent? } } else {
QgsRectangle QgsInterpolationDialog::boundingBoxOfLayers() { int nLayers = mLayersTreeWidget->topLevelItemCount(); QgsRectangle combinedLayerExtent; for ( int i = 0; i < nLayers; ++i ) { QString layerName = mLayersTreeWidget->topLevelItem( i )->text( 0 ); QgsVectorLayer* theVectorLayer = vectorLayerFromName( layerName ); if ( !theVectorLayer ) { continue; } QgsVectorDataProvider* theProvider = theVectorLayer->dataProvider(); if ( !theProvider ) { continue; } //update extent QgsRectangle currentLayerExtent = theVectorLayer->extent(); if ( combinedLayerExtent.isEmpty() ) { combinedLayerExtent = currentLayerExtent; } else { combinedLayerExtent.combineExtentWith( ¤tLayerExtent ); } } return combinedLayerExtent; }
void QgsServerProjectParser::combineExtentAndCrsOfGroupChildren( QDomElement& groupElem, QDomDocument& doc, bool considerMapExtent ) const { QgsRectangle combinedBBox; QSet<QString> combinedCRSSet; bool firstBBox = true; bool firstCRSSet = true; QDomNodeList layerChildren = groupElem.childNodes(); for ( int j = 0; j < layerChildren.size(); ++j ) { QDomElement childElem = layerChildren.at( j ).toElement(); if ( childElem.tagName() != "Layer" ) continue; QgsRectangle bbox = layerBoundingBoxInProjectCrs( childElem, doc ); if ( !bbox.isEmpty() ) { if ( firstBBox ) { combinedBBox = bbox; firstBBox = false; } else { combinedBBox.combineExtentWith( bbox ); } } //combine crs set QSet<QString> crsSet; if ( crsSetForLayer( childElem, crsSet ) ) { if ( firstCRSSet ) { combinedCRSSet = crsSet; firstCRSSet = false; } else { combinedCRSSet.intersect( crsSet ); } } } QgsConfigParserUtils::appendCrsElementsToLayer( groupElem, doc, combinedCRSSet.toList(), supportedOutputCrsList() ); QgsCoordinateReferenceSystem groupCRS = projectCrs(); if ( considerMapExtent ) { QgsRectangle mapRect = mapRectangle(); if ( !mapRect.isEmpty() ) { combinedBBox = mapRect; } } QgsConfigParserUtils::appendLayerBoundingBoxes( groupElem, doc, combinedBBox, groupCRS, combinedCRSSet.toList(), supportedOutputCrsList() ); }
void TestQgsRectangle::combine() { QgsRectangle rect; // combine extent of null rectangle with valid rectangle rect.combineExtentWith( QgsRectangle( 1, 2, 3, 4 ) ); QCOMPARE( rect.xMinimum(), 1.0 ); QCOMPARE( rect.yMinimum(), 2.0 ); QCOMPARE( rect.xMaximum(), 3.0 ); QCOMPARE( rect.yMaximum(), 4.0 ); // combine extent of valid rectangle with null rectangle rect = QgsRectangle( 1, 2, 3, 4 ); rect.combineExtentWith( QgsRectangle() ); QCOMPARE( rect.xMinimum(), 1.0 ); QCOMPARE( rect.yMinimum(), 2.0 ); QCOMPARE( rect.xMaximum(), 3.0 ); QCOMPARE( rect.yMaximum(), 4.0 ); }
QgsRectangle QgsMapSettings::fullExtent() const { // reset the map canvas extent since the extent may now be smaller // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction QgsRectangle fullExtent; fullExtent.setMinimal(); // iterate through the map layers and test each layers extent // against the current min and max values QgsDebugMsgLevel( QStringLiteral( "Layer count: %1" ).arg( mLayers.count() ), 5 ); const auto constMLayers = mLayers; for ( const QgsWeakMapLayerPointer &layerPtr : constMLayers ) { if ( QgsMapLayer *lyr = layerPtr.data() ) { QgsDebugMsgLevel( "Updating extent using " + lyr->name(), 5 ); QgsDebugMsgLevel( "Input extent: " + lyr->extent().toString(), 5 ); if ( lyr->extent().isNull() ) continue; // Layer extents are stored in the coordinate system (CS) of the // layer. The extent must be projected to the canvas CS QgsRectangle extent = layerExtentToOutputExtent( lyr, lyr->extent() ); QgsDebugMsgLevel( "Output extent: " + extent.toString(), 5 ); fullExtent.combineExtentWith( extent ); } } if ( fullExtent.width() == 0.0 || fullExtent.height() == 0.0 ) { // If all of the features are at the one point, buffer the // rectangle a bit. If they are all at zero, do something a bit // more crude. if ( fullExtent.xMinimum() == 0.0 && fullExtent.xMaximum() == 0.0 && fullExtent.yMinimum() == 0.0 && fullExtent.yMaximum() == 0.0 ) { fullExtent.set( -1.0, -1.0, 1.0, 1.0 ); } else { const double padFactor = 1e-8; double widthPad = fullExtent.xMinimum() * padFactor; double heightPad = fullExtent.yMinimum() * padFactor; double xmin = fullExtent.xMinimum() - widthPad; double xmax = fullExtent.xMaximum() + widthPad; double ymin = fullExtent.yMinimum() - heightPad; double ymax = fullExtent.yMaximum() + heightPad; fullExtent.set( xmin, ymin, xmax, ymax ); } } QgsDebugMsgLevel( "Full extent: " + fullExtent.toString(), 5 ); return fullExtent; }
//! Returns whether the device-geometry can be replaced by its BBOX when is applied the specified tolerance bool QgsAbstractGeometrySimplifier::isGeneralizableByDeviceBoundingBox( const QVector<QPointF>& points, float mapToPixelTol ) { QgsRectangle r; r.setMinimal(); for ( int i = 0, numPoints = points.size(); i < numPoints; ++i ) { r.combineExtentWith( points[i].x(), points[i].y() ); } return isGeneralizableByDeviceBoundingBox( r, mapToPixelTol ); }
void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent ) { if ( !mCanvas ) return; if ( !f.isValid() ) { f = referencedFeature(); if ( !f.isValid() ) return; } if ( !f.hasGeometry() ) { return; } QgsGeometry geom = f.geometry(); // scale or pan if ( canvasExtent == Scale ) { QgsRectangle featBBox = geom.boundingBox(); featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox ); QgsRectangle extent = mCanvas->extent(); if ( !extent.contains( featBBox ) ) { extent.combineExtentWith( featBBox ); extent.scale( 1.1 ); mCanvas->setExtent( extent ); mCanvas->refresh(); } } else if ( canvasExtent == Pan ) { QgsGeometry centroid = geom.centroid(); QgsPointXY center = centroid.asPoint(); center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center ); mCanvas->zoomByFactor( 1.0, ¢er ); // refresh is done in this method } // highlight deleteHighlight(); mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer ); QgsIdentifyMenu::styleHighlight( mHighlight ); mHighlight->show(); QTimer *timer = new QTimer( this ); timer->setSingleShot( true ); connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight ); timer->start( 3000 ); }
void QgsExpressionSelectionDialog::mButtonZoomToFeatures_clicked() { if ( mExpressionBuilder->expressionText().isEmpty() || !mMapCanvas ) return; QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( mExpressionBuilder->expressionText() ) .setExpressionContext( context ) .setNoAttributes(); QgsFeatureIterator features = mLayer->getFeatures( request ); QgsRectangle bbox; bbox.setMinimal(); QgsFeature feat; int featureCount = 0; while ( features.nextFeature( feat ) ) { QgsGeometry geom = feat.geometry(); if ( geom.isNull() || geom.constGet()->isEmpty() ) continue; QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() ); bbox.combineExtentWith( r ); featureCount++; } features.close(); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); if ( featureCount > 0 ) { mMapCanvas->zoomToFeatureExtent( bbox ); if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ), Qgis::Info, timeout ); } } else if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "No matching features found" ), Qgis::Info, timeout ); } saveRecent(); }
void QgsProjectParser::combineExtentAndCrsOfGroupChildren( QDomElement& groupElem, QDomDocument& doc ) const { QgsRectangle combinedBBox; QSet<QString> combinedCRSSet; bool firstBBox = true; bool firstCRSSet = true; QDomNodeList layerChildren = groupElem.childNodes(); for ( int j = 0; j < layerChildren.size(); ++j ) { QDomElement childElem = layerChildren.at( j ).toElement(); if ( childElem.tagName() != "Layer" ) continue; QgsRectangle bbox = layerBoundingBoxInProjectCRS( childElem ); if ( !bbox.isEmpty() ) { if ( firstBBox ) { combinedBBox = bbox; firstBBox = false; } else { combinedBBox.combineExtentWith( &bbox ); } } //combine crs set QSet<QString> crsSet; if ( crsSetForLayer( childElem, crsSet ) ) { if ( firstCRSSet ) { combinedCRSSet = crsSet; firstCRSSet = false; } else { combinedCRSSet.intersect( crsSet ); } } } appendCRSElementsToLayer( groupElem, doc, combinedCRSSet.toList() ); const QgsCoordinateReferenceSystem& groupCRS = projectCRS(); appendLayerBoundingBoxes( groupElem, doc, combinedBBox, groupCRS ); }
void QgsSelectByFormDialog::zoomToFeatures( const QString &filter ) { QgsFeatureIds ids; QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) .setExpressionContext( context ) .setSubsetOfAttributes( QgsAttributeList() ); QgsFeatureIterator features = mLayer->getFeatures( request ); QgsRectangle bbox; bbox.setMinimal(); QgsFeature feat; int featureCount = 0; while ( features.nextFeature( feat ) ) { QgsGeometry geom = feat.geometry(); if ( geom.isNull() || geom.geometry()->isEmpty() ) continue; QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() ); bbox.combineExtentWith( r ); featureCount++; } features.close(); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); if ( featureCount > 0 ) { mMapCanvas->zoomToFeatureExtent( bbox ); if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ), QgsMessageBar::INFO, timeout ); } } else if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "No matching features found" ), QgsMessageBar::INFO, timeout ); } }
QgsRectangle QgsGeometryCollection::calculateBoundingBox() const { if ( mGeometries.empty() ) { return QgsRectangle(); } QgsRectangle bbox = mGeometries.at( 0 )->boundingBox(); for ( int i = 1; i < mGeometries.size(); ++i ) { QgsRectangle geomBox = mGeometries.at( i )->boundingBox(); bbox.combineExtentWith( geomBox ); } return bbox; }
QgsRectangle QgsCompoundCurve::calculateBoundingBox() const { if ( mCurves.empty() ) { return QgsRectangle(); } QgsRectangle bbox = mCurves.at( 0 )->boundingBox(); for ( int i = 1; i < mCurves.size(); ++i ) { QgsRectangle curveBox = mCurves.at( i )->boundingBox(); bbox.combineExtentWith( curveBox ); } return bbox; }
QgsRectangle QgsFeatureSource::sourceExtent() const { QgsRectangle r; QgsFeatureRequest req; req.setNoAttributes(); QgsFeatureIterator it = getFeatures( req ); QgsFeature f; while ( it.nextFeature( f ) ) { if ( f.hasGeometry() ) r.combineExtentWith( f.geometry().boundingBox() ); } return r; }
//! Returns the BBOX of the specified WKB-point stream inline static QgsRectangle calculateBoundingBox( QGis::WkbType wkbType, QgsConstWkbPtr wkbPtr, int numPoints ) { QgsRectangle r; r.setMinimal(); int skipZM = ( QGis::wkbDimensions( wkbType ) - 2 ) * sizeof( double ); Q_ASSERT( skipZM >= 0 ); for ( int i = 0; i < numPoints; ++i ) { double x, y; wkbPtr >> x >> y; wkbPtr += skipZM; r.combineExtentWith( x, y ); } return r; }
//! Returns the BBOX of the specified WKB-point stream inline static QgsRectangle calculateBoundingBox( QGis::WkbType wkbType, const unsigned char* wkb, size_t numPoints ) { double x, y; QgsRectangle r; r.setMinimal(); int sizeOfDoubleX = sizeof( double ); int sizeOfDoubleY = QGis::wkbDimensions( wkbType ) == 3 /*hasZValue*/ ? 2 * sizeof( double ) : sizeof( double ); for ( size_t i = 0; i < numPoints; ++i ) { memcpy( &x, wkb, sizeof( double ) ); wkb += sizeOfDoubleX; memcpy( &y, wkb, sizeof( double ) ); wkb += sizeOfDoubleY; r.combineExtentWith( x, y ); } return r; }
void QgsVertexEditor::updateVertexSelection( const QItemSelection &, const QItemSelection & ) { if ( !mLockedFeature || mUpdatingVertexSelection || mUpdatingTableSelection ) return; mUpdatingVertexSelection = true; mLockedFeature->deselectAllVertices(); QgsCoordinateTransform t( mLockedFeature->layer()->crs(), mCanvas->mapSettings().destinationCrs(), QgsProject::instance() ); std::unique_ptr<QgsRectangle> bbox; const QModelIndexList indexList = mTableView->selectionModel()->selectedRows(); for ( const QModelIndex &index : indexList ) { int vertexIdx = index.row(); mLockedFeature->selectVertex( vertexIdx ); // create a bounding box of selected vertices QgsPointXY point( mLockedFeature->vertexMap().at( vertexIdx )->point() ); if ( !bbox ) bbox.reset( new QgsRectangle( point, point ) ); else bbox->combineExtentWith( point ); } //ensure that newly selected vertices are visible in canvas if ( bbox ) { try { QgsRectangle transformedBbox = t.transform( *bbox ); QgsRectangle canvasExtent = mCanvas->mapSettings().extent(); transformedBbox.combineExtentWith( canvasExtent ); mCanvas->setExtent( transformedBbox ); } catch ( QgsCsException &cse ) { QgsMessageLog::logMessage( QObject::tr( "Simplify transform error caught: %1" ).arg( cse.what() ), QObject::tr( "CRS" ) ); } } mUpdatingVertexSelection = false; }
QgsRectangle QgsDxfExport::dxfExtent() const { QgsRectangle extent; QList< QgsMapLayer* >::const_iterator layerIt = mLayers.constBegin(); for ( ; layerIt != mLayers.constEnd(); ++layerIt ) { if ( *layerIt ) { if ( extent.isEmpty() ) { extent = ( *layerIt )->extent(); } else { QgsRectangle layerExtent = ( *layerIt )->extent(); extent.combineExtentWith( &layerExtent ); } } } return extent; }
//! Simplify the WKB-geometry using the specified tolerance bool QgsMapToPixelSimplifier::simplifyWkbGeometry( int simplifyFlags, QGis::WkbType wkbType, const unsigned char* sourceWkb, size_t sourceWkbSize, unsigned char* targetWkb, size_t& targetWkbSize, const QgsRectangle& envelope, double map2pixelTol, bool writeHeader, bool isaLinearRing ) { bool isGeneralizable = true; bool hasZValue = QGis::wkbDimensions( wkbType ) == 3; bool result = false; // Save initial WKB settings to use when the simplification creates invalid geometries const unsigned char* sourcePrevWkb = sourceWkb; unsigned char* targetPrevWkb = targetWkb; size_t targetWkbPrevSize = targetWkbSize; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { isGeneralizable = generalizeWkbGeometryByBoundingBox( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, envelope, writeHeader ); if ( isGeneralizable ) return true; } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; // Write the main header of the geometry if ( writeHeader ) { targetWkb[0] = sourceWkb[0]; // byteOrder sourceWkb += 1; targetWkb += 1; int geometryType; memcpy( &geometryType, sourceWkb, 4 ); int flatType = QGis::flatType(( QGis::WkbType )geometryType ); memcpy( targetWkb, &flatType, 4 ); // type sourceWkb += 4; targetWkb += 4; targetWkbSize += 5; } const unsigned char* wkb1 = sourceWkb; unsigned char* wkb2 = targetWkb; unsigned int flatType = QGis::flatType( wkbType ); // Write the geometry if ( flatType == QGis::WKBLineString || isaLinearRing ) { double x, y, lastX = 0, lastY = 0; QgsRectangle r; r.setMinimal(); int sizeOfDoubleX = sizeof( double ); int sizeOfDoubleY = QGis::wkbDimensions( wkbType ) == 3 /*hasZValue*/ ? 2 * sizeof( double ) : sizeof( double ); int numPoints; memcpy( &numPoints, sourceWkb, 4 ); sourceWkb += 4; if ( numPoints <= ( isaLinearRing ? 5 : 2 ) ) isGeneralizable = false; int numTargetPoints = 0; memcpy( targetWkb, &numTargetPoints, 4 ); targetWkb += 4; targetWkbSize += 4; double* ptr = ( double* )targetWkb; map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations. bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { double x1, y1, x2, y2; const unsigned char* startWkbX = sourceWkb; const unsigned char* startWkbY = startWkbX + sizeOfDoubleX; const unsigned char* finalWkbX = sourceWkb + ( numPoints - 1 ) * ( sizeOfDoubleX + sizeOfDoubleY ); const unsigned char* finalWkbY = finalWkbX + sizeOfDoubleX; memcpy( &x1, startWkbX, sizeof( double ) ); memcpy( &y1, startWkbY, sizeof( double ) ); memcpy( &x2, finalWkbX, sizeof( double ) ); memcpy( &y2, finalWkbY, sizeof( double ) ); isaLinearRing = ( x1 == x2 ) && ( y1 == y2 ); } // Process each vertex... for ( int i = 0; i < numPoints; ++i ) { memcpy( &x, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleX; memcpy( &y, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleY; isLongSegment = false; if ( i == 0 || !isGeneralizable || ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { memcpy( ptr, &x, sizeof( double ) ); lastX = x; ptr++; memcpy( ptr, &y, sizeof( double ) ); lastY = y; ptr++; numTargetPoints++; hasLongSegments |= isLongSegment; } r.combineExtentWith( x, y ); } targetWkb = wkb2 + 4; if ( numTargetPoints < ( isaLinearRing ? 4 : 2 ) ) { // we simplified the geometry too much! if ( !hasLongSegments ) { // approximate the geometry's shape by its bounding box // (rect for linear ring / one segment for line string) unsigned char* targetTempWkb = targetWkb; size_t targetWkbTempSize = targetWkbSize; sourceWkb = sourcePrevWkb; targetWkb = targetPrevWkb; targetWkbSize = targetWkbPrevSize; if ( generalizeWkbGeometryByBoundingBox( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, r, writeHeader ) ) return true; targetWkb = targetTempWkb; targetWkbSize = targetWkbTempSize; } else { // Bad luck! The simplified geometry is invalid and approximation by bounding box // would create artifacts due to long segments. Worst of all, we may have overwritten // the original coordinates by the simplified ones (source and target WKB ptr can be the same) // so we cannot even undo the changes here. We will return invalid geometry and hope that // other pieces of QGIS will survive that :-/ } } if ( isaLinearRing ) { // make sure we keep the linear ring closed memcpy( &x, targetWkb + 0, sizeof( double ) ); memcpy( &y, targetWkb + sizeof( double ), sizeof( double ) ); if ( lastX != x || lastY != y ) { memcpy( ptr, &x, sizeof( double ) ); ptr++; memcpy( ptr, &y, sizeof( double ) ); ptr++; numTargetPoints++; } } targetWkbSize += numTargetPoints * sizeof( double ) * 2; targetWkb = wkb2; memcpy( targetWkb, &numTargetPoints, 4 ); result = numPoints != numTargetPoints; } else if ( flatType == QGis::WKBPolygon ) { int numRings; memcpy( &numRings, sourceWkb, 4 ); sourceWkb += 4; memcpy( targetWkb, &numRings, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numRings; ++i ) { int numPoints_i; memcpy( &numPoints_i, sourceWkb, 4 ); QgsRectangle envelope_i = numRings == 1 ? envelope : calculateBoundingBox( wkbType, sourceWkb + 4, numPoints_i ); size_t sourceWkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); size_t targetWkbSize_i = 0; result |= simplifyWkbGeometry( simplifyFlags, wkbType, sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope_i, map2pixelTol, false, true ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } else if ( flatType == QGis::WKBMultiLineString || flatType == QGis::WKBMultiPolygon ) { int numGeoms; memcpy( &numGeoms, sourceWkb, 4 ); sourceWkb += 4; wkb1 += 4; memcpy( targetWkb, &numGeoms, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numGeoms; ++i ) { size_t sourceWkbSize_i = 0; size_t targetWkbSize_i = 0; // ... calculate the wkb-size of the current child complex geometry if ( flatType == QGis::WKBMultiLineString ) { int numPoints_i; memcpy( &numPoints_i, wkb1 + 5, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += 5 + wkbSize_i; wkb1 += 5 + wkbSize_i; } else { int numPrings_i; memcpy( &numPrings_i, wkb1 + 5, 4 ); sourceWkbSize_i = 9; wkb1 += 9; for ( int j = 0; j < numPrings_i; ++j ) { int numPoints_i; memcpy( &numPoints_i, wkb1, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += wkbSize_i; wkb1 += wkbSize_i; } } result |= simplifyWkbGeometry( simplifyFlags, QGis::singleType( wkbType ), sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope, map2pixelTol, true, false ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } return result; }
//! Simplify the WKB-geometry using the specified tolerance bool QgsMapToPixelSimplifier::simplifyWkbGeometry( int simplifyFlags, QGis::WkbType wkbType, unsigned char* sourceWkb, size_t sourceWkbSize, unsigned char* targetWkb, size_t& targetWkbSize, const QgsRectangle& envelope, double map2pixelTol, bool writeHeader, bool isaLinearRing ) { bool isGeneralizable = true; bool hasZValue = QGis::wkbDimensions( wkbType ) == 3; bool result = false; // Save initial WKB settings to use when the simplification creates invalid geometries unsigned char* sourcePrevWkb = sourceWkb; unsigned char* targetPrevWkb = targetWkb; size_t targetWkbPrevSize = targetWkbSize; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { isGeneralizable = generalizeWkbGeometry( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, envelope, writeHeader ); if ( isGeneralizable ) return true; } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; // Write the main header of the geometry if ( writeHeader ) { targetWkb[0] = sourceWkb[0]; // byteOrder sourceWkb += 1; targetWkb += 1; int geometryType; memcpy( &geometryType, sourceWkb, 4 ); int flatType = QGis::flatType(( QGis::WkbType )geometryType ); memcpy( targetWkb, &flatType, 4 ); // type sourceWkb += 4; targetWkb += 4; targetWkbSize += 5; } unsigned char* wkb1 = sourceWkb; unsigned char* wkb2 = targetWkb; unsigned int flatType = QGis::flatType( wkbType ); // Write the geometry if ( flatType == QGis::WKBLineString || isaLinearRing ) { double x, y, lastX = 0, lastY = 0; QgsRectangle r; r.setMinimal(); int sizeOfDoubleX = sizeof( double ); int sizeOfDoubleY = QGis::wkbDimensions( wkbType ) == 3 /*hasZValue*/ ? 2 * sizeof( double ) : sizeof( double ); int numPoints; memcpy( &numPoints, sourceWkb, 4 ); sourceWkb += 4; if ( numPoints <= ( isaLinearRing ? 5 : 2 ) ) isGeneralizable = false; int numTargetPoints = 0; memcpy( targetWkb, &numTargetPoints, 4 ); targetWkb += 4; targetWkbSize += 4; double* ptr = ( double* )targetWkb; map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { double x1, y1, x2, y2; unsigned char* startWkbX = sourceWkb; unsigned char* startWkbY = startWkbX + sizeOfDoubleX; unsigned char* finalWkbX = sourceWkb + ( numPoints - 1 ) * ( sizeOfDoubleX + sizeOfDoubleY ); unsigned char* finalWkbY = finalWkbX + sizeOfDoubleX; memcpy( &x1, startWkbX, sizeof( double ) ); memcpy( &y1, startWkbY, sizeof( double ) ); memcpy( &x2, finalWkbX, sizeof( double ) ); memcpy( &y2, finalWkbY, sizeof( double ) ); isaLinearRing = ( x1 == x2 ) && ( y1 == y2 ); } // Process each vertex... for ( int i = 0; i < numPoints; ++i ) { memcpy( &x, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleX; memcpy( &y, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleY; if ( i == 0 || !isGeneralizable || calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { memcpy( ptr, &x, sizeof( double ) ); lastX = x; ptr++; memcpy( ptr, &y, sizeof( double ) ); lastY = y; ptr++; numTargetPoints++; } r.combineExtentWith( x, y ); } targetWkb = wkb2 + 4; // Fix the topology of the geometry if ( numTargetPoints <= ( isaLinearRing ? 2 : 1 ) ) { unsigned char* targetTempWkb = targetWkb; int targetWkbTempSize = targetWkbSize; sourceWkb = sourcePrevWkb; targetWkb = targetPrevWkb; targetWkbSize = targetWkbPrevSize; if ( generalizeWkbGeometry( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, r, writeHeader ) ) return true; targetWkb = targetTempWkb; targetWkbSize = targetWkbTempSize; } if ( isaLinearRing ) { memcpy( &x, targetWkb + 0, sizeof( double ) ); memcpy( &y, targetWkb + sizeof( double ), sizeof( double ) ); if ( lastX != x || lastY != y ) { memcpy( ptr, &x, sizeof( double ) ); ptr++; memcpy( ptr, &y, sizeof( double ) ); ptr++; numTargetPoints++; } } targetWkbSize += numTargetPoints * sizeof( double ) * 2; targetWkb = wkb2; memcpy( targetWkb, &numTargetPoints, 4 ); result = numPoints != numTargetPoints; } else if ( flatType == QGis::WKBPolygon ) { int numRings; memcpy( &numRings, sourceWkb, 4 ); sourceWkb += 4; memcpy( targetWkb, &numRings, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numRings; ++i ) { int numPoints_i; memcpy( &numPoints_i, sourceWkb, 4 ); QgsRectangle envelope_i = numRings == 1 ? envelope : calculateBoundingBox( wkbType, sourceWkb + 4, numPoints_i ); size_t sourceWkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); size_t targetWkbSize_i = 0; result |= simplifyWkbGeometry( simplifyFlags, wkbType, sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope_i, map2pixelTol, false, true ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } else if ( flatType == QGis::WKBMultiLineString || flatType == QGis::WKBMultiPolygon ) { int numGeoms; memcpy( &numGeoms, sourceWkb, 4 ); sourceWkb += 4; wkb1 += 4; memcpy( targetWkb, &numGeoms, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numGeoms; ++i ) { size_t sourceWkbSize_i = 0; size_t targetWkbSize_i = 0; // ... calculate the wkb-size of the current child complex geometry if ( flatType == QGis::WKBMultiLineString ) { int numPoints_i; memcpy( &numPoints_i, wkb1 + 5, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += 5 + wkbSize_i; wkb1 += 5 + wkbSize_i; } else { int numPrings_i; memcpy( &numPrings_i, wkb1 + 5, 4 ); sourceWkbSize_i = 9; wkb1 += 9; for ( int j = 0; j < numPrings_i; ++j ) { int numPoints_i; memcpy( &numPoints_i, wkb1, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += wkbSize_i; wkb1 += wkbSize_i; } } result |= simplifyWkbGeometry( simplifyFlags, QGis::singleType( wkbType ), sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope, map2pixelTol, true, false ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } return result; }
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; }
ErrorList topolTest::checkCloseFeature( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) { Q_UNUSED( isExtent ); ErrorList errorList; QgsSpatialIndex* index = 0; bool badG1 = false, badG2 = false; bool skipItself = layer1 == layer2; int i = 0; QList<FeatureLayer>::Iterator it; QList<FeatureLayer>::ConstIterator FeatureListEnd = mFeatureList1.end(); for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) { if ( !( ++i % 100 ) ) emit progress( i ); if ( testCancelled() ) break; QgsGeometry* g1 = it->feature.geometry(); if ( !g1 || !g1->asGeos() ) { badG1 = true; continue; } QgsRectangle bb = g1->boundingBox(); // increase bounding box by tolerance QgsRectangle frame( bb.xMinimum() - tolerance, bb.yMinimum() - tolerance, bb.xMaximum() + tolerance, bb.yMaximum() + tolerance ); QList<QgsFeatureId> crossingIds; crossingIds = index->intersects( frame ); QList<QgsFeatureId>::Iterator cit = crossingIds.begin(); QList<QgsFeatureId>::ConstIterator crossingIdsEnd = crossingIds.end(); for ( ; cit != crossingIdsEnd; ++cit ) { QgsFeature& f = mFeatureMap2[*cit].feature; QgsGeometry* g2 = f.geometry(); // skip itself, when invoked with the same layer if ( skipItself && f.id() == it->feature.id() ) continue; if ( !g2 || !g2->asGeos() ) { badG2 = true; continue; } if ( g1->distance( *g2 ) < tolerance ) { QgsRectangle r = g2->boundingBox(); r.combineExtentWith( &bb ); QList<FeatureLayer> fls; FeatureLayer fl; fl.feature = f; fl.layer = layer2; fls << *it << fl; QgsGeometry* conflict = new QgsGeometry( *g2 ); TopolErrorClose* err = new TopolErrorClose( r, conflict, fls ); //TopolErrorClose* err = new TopolErrorClose(r, g2, fls); errorList << err; } } } if ( badG2 ) QgsMessageLog::logMessage( tr( "Invalid second geometry." ), tr( "Topology plugin" ) ); if ( badG1 ) QgsMessageLog::logMessage( tr( "Invalid first geometry." ), tr( "Topology plugin" ) ); return errorList; }
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; }
void QgsGeometryCheckerResultTab::highlightErrors( bool current ) { qDeleteAll( mCurrentRubberBands ); mCurrentRubberBands.clear(); QList<QTableWidgetItem *> items; QVector<QgsPointXY> errorPositions; QgsRectangle totextent; if ( current ) { items.append( ui.tableWidgetErrors->currentItem() ); } else { items.append( ui.tableWidgetErrors->selectedItems() ); } for ( QTableWidgetItem *item : qgis::as_const( items ) ) { QgsGeometryCheckError *error = ui.tableWidgetErrors->item( item->row(), 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError *>(); const QgsGeometry geom = error->geometry(); if ( ui.checkBoxHighlight->isChecked() && !geom.isNull() ) { QgsRubberBand *featureRubberBand = new QgsRubberBand( mIface->mapCanvas() ); featureRubberBand->addGeometry( geom, nullptr ); featureRubberBand->setWidth( 5 ); featureRubberBand->setColor( Qt::yellow ); mCurrentRubberBands.append( featureRubberBand ); } if ( ui.radioButtonError->isChecked() || current || error->status() == QgsGeometryCheckError::StatusFixed ) { QgsRubberBand *pointRubberBand = new QgsRubberBand( mIface->mapCanvas(), QgsWkbTypes::PointGeometry ); pointRubberBand->addPoint( error->location() ); pointRubberBand->setWidth( 20 ); pointRubberBand->setColor( Qt::red ); mCurrentRubberBands.append( pointRubberBand ); errorPositions.append( error->location() ); } else if ( ui.radioButtonFeature->isChecked() ) { QgsRectangle geomextent = error->affectedAreaBBox(); if ( totextent.isEmpty() ) { totextent = geomextent; } else { totextent.combineExtentWith( geomextent ); } } } // If error positions positions are marked, pan to the center of all positions, // and zoom out if necessary to make all points fit. if ( !errorPositions.isEmpty() ) { double cx = 0., cy = 0.; QgsRectangle pointExtent( errorPositions.first(), errorPositions.first() ); Q_FOREACH ( const QgsPointXY &p, errorPositions ) { cx += p.x(); cy += p.y(); pointExtent.include( p ); }
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 ) ); } }
//! Simplify the WKB-geometry using the specified tolerance QgsGeometry QgsMapToPixelSimplifier::simplifyGeometry( int simplifyFlags, SimplifyAlgorithm simplifyAlgorithm, QgsWkbTypes::Type wkbType, const QgsAbstractGeometry& geometry, const QgsRectangle &envelope, double map2pixelTol, bool isaLinearRing ) { bool isGeneralizable = true; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope ); } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; const QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( wkbType ); // Write the geometry if ( flatType == QgsWkbTypes::LineString || flatType == QgsWkbTypes::CircularString ) { const QgsCurve& srcCurve = dynamic_cast<const QgsCurve&>( geometry ); QScopedPointer<QgsCurve> output( createEmptySameTypeGeom( srcCurve ) ); double x = 0.0, y = 0.0, lastX = 0.0, lastY = 0.0; QgsRectangle r; r.setMinimal(); const int numPoints = srcCurve.numPoints(); if ( numPoints <= ( isaLinearRing ? 4 : 2 ) ) isGeneralizable = false; bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { isaLinearRing = qgsDoubleNear( srcCurve.xAt( 0 ), srcCurve.xAt( numPoints - 1 ) ) && qgsDoubleNear( srcCurve.yAt( 0 ), srcCurve.yAt( numPoints - 1 ) ); } // Process each vertex... switch ( simplifyAlgorithm ) { case SnapToGrid: { double gridOriginX = envelope.xMinimum(); double gridOriginY = envelope.yMinimum(); // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f; for ( int i = 0; i < numPoints; ++i ) { x = srcCurve.xAt( i ); y = srcCurve.yAt( i ); if ( i == 0 || !isGeneralizable || !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) ); lastX = x; lastY = y; } r.combineExtentWith( x, y ); } break; } case Visvalingam: { map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations. EFFECTIVE_AREAS ea( srcCurve ); int set_area = 0; ptarray_calc_areas( &ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol ); for ( int i = 0; i < numPoints; ++i ) { if ( ea.res_arealist[ i ] > map2pixelTol ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea.inpts.at( i ) ); } } break; } case Distance: { map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations. for ( int i = 0; i < numPoints; ++i ) { x = srcCurve.xAt( i ); y = srcCurve.yAt( i ); isLongSegment = false; if ( i == 0 || !isGeneralizable || ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) ); lastX = x; lastY = y; hasLongSegments |= isLongSegment; } r.combineExtentWith( x, y ); } } } if ( output->numPoints() < ( isaLinearRing ? 4 : 2 ) ) { // we simplified the geometry too much! if ( !hasLongSegments ) { // approximate the geometry's shape by its bounding box // (rect for linear ring / one segment for line string) return generalizeWkbGeometryByBoundingBox( wkbType, geometry, r ); } else { // Bad luck! The simplified geometry is invalid and approximation by bounding box // would create artifacts due to long segments. // We will return the original geometry return QgsGeometry( geometry.clone() ); } } if ( isaLinearRing ) { // make sure we keep the linear ring closed if ( !qgsDoubleNear( lastX, output->xAt( 0 ) ) || !qgsDoubleNear( lastY, output->yAt( 0 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( output->xAt( 0 ), output->yAt( 0 ) ) ); } } return QgsGeometry( output.take() ); } else if ( flatType == QgsWkbTypes::Polygon ) { const QgsPolygonV2& srcPolygon = dynamic_cast<const QgsPolygonV2&>( geometry ); QScopedPointer<QgsPolygonV2> polygon( new QgsPolygonV2() ); polygon->setExteriorRing( dynamic_cast<QgsCurve*>( simplifyGeometry( simplifyFlags, simplifyAlgorithm, srcPolygon.exteriorRing()->wkbType(), *srcPolygon.exteriorRing(), envelope, map2pixelTol, true ).geometry()->clone() ) ); for ( int i = 0; i < srcPolygon.numInteriorRings(); ++i ) { const QgsCurve* sub = srcPolygon.interiorRing( i ); polygon->addInteriorRing( dynamic_cast<QgsCurve*>( simplifyGeometry( simplifyFlags, simplifyAlgorithm, sub->wkbType(), *sub, envelope, map2pixelTol, true ).geometry()->clone() ) ); } return QgsGeometry( polygon.take() ); } else if ( QgsWkbTypes::isMultiType( flatType ) ) { const QgsGeometryCollection& srcCollection = dynamic_cast<const QgsGeometryCollection&>( geometry ); QScopedPointer<QgsGeometryCollection> collection( createEmptySameTypeGeom( srcCollection ) ); const int numGeoms = srcCollection.numGeometries(); for ( int i = 0; i < numGeoms; ++i ) { const QgsAbstractGeometry* sub = srcCollection.geometryN( i ); collection->addGeometry( simplifyGeometry( simplifyFlags, simplifyAlgorithm, sub->wkbType(), *sub, envelope, map2pixelTol, false ).geometry()->clone() ); } return QgsGeometry( collection.take() ); } return QgsGeometry( geometry.clone() ); }
//! Simplify the WKB-geometry using the specified tolerance bool QgsMapToPixelSimplifier::simplifyWkbGeometry( int simplifyFlags, SimplifyAlgorithm simplifyAlgorithm, QGis::WkbType wkbType, QgsConstWkbPtr sourceWkbPtr, QgsWkbPtr targetWkbPtr, int &targetWkbSize, const QgsRectangle &envelope, double map2pixelTol, bool writeHeader, bool isaLinearRing ) { bool isGeneralizable = true; bool result = false; // Save initial WKB settings to use when the simplification creates invalid geometries QgsConstWkbPtr sourcePrevWkbPtr( sourceWkbPtr ); QgsWkbPtr targetPrevWkbPtr( targetWkbPtr ); int targetWkbPrevSize = targetWkbSize; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { isGeneralizable = generalizeWkbGeometryByBoundingBox( wkbType, sourceWkbPtr, targetWkbPtr, targetWkbSize, envelope, writeHeader ); if ( isGeneralizable ) return true; } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; // Write the main header of the geometry if ( writeHeader ) { QgsWKBTypes::Type geometryType = sourceWkbPtr.readHeader(); targetWkbPtr << ( char ) QgsApplication::endian() << QgsWKBTypes::flatType( geometryType ); targetWkbSize += targetWkbPtr - targetPrevWkbPtr; } unsigned int flatType = QGis::flatType( wkbType ); // Write the geometry if ( flatType == QGis::WKBLineString || isaLinearRing ) { QgsWkbPtr savedTargetWkbPtr( targetWkbPtr ); double x = 0.0, y = 0.0, lastX = 0, lastY = 0; QgsRectangle r; r.setMinimal(); int skipZM = ( QGis::wkbDimensions( wkbType ) - 2 ) * sizeof( double ); Q_ASSERT( skipZM >= 0 ); int numPoints; sourceWkbPtr >> numPoints; if ( numPoints <= ( isaLinearRing ? 5 : 2 ) ) isGeneralizable = false; QgsWkbPtr numPtr( targetWkbPtr ); int numTargetPoints = 0; targetWkbPtr << numTargetPoints; targetWkbSize += 4; bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. bool badLuck = false; // Check whether the LinearRing is really closed. if ( isaLinearRing ) { QgsConstWkbPtr checkPtr( sourceWkbPtr ); double x1, y1, x2, y2; checkPtr >> x1 >> y1; checkPtr += skipZM + ( numPoints - 2 ) * ( 2 * sizeof( double ) + skipZM ); checkPtr >> x2 >> y2; isaLinearRing = qgsDoubleNear( x1, x2 ) && qgsDoubleNear( y1, y2 ); } // Process each vertex... if ( simplifyAlgorithm == SnapToGrid ) { double gridOriginX = envelope.xMinimum(); double gridOriginY = envelope.yMinimum(); // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f; for ( int i = 0; i < numPoints; ++i ) { sourceWkbPtr >> x >> y; sourceWkbPtr += skipZM; if ( i == 0 || !isGeneralizable || !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { targetWkbPtr << x << y; lastX = x; lastY = y; numTargetPoints++; } r.combineExtentWith( x, y ); } }
ErrorList topolTest::checkOverlapWithLayer( QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) { int i = 0; ErrorList errorList; bool skipItself = layer1 == layer2; QgsSpatialIndex *index = mLayerIndexes[layer2->id()]; QgsGeometry canvasExtentPoly = QgsGeometry::fromWkt( qgsInterface->mapCanvas()->extent().asWktPolygon() ); QList<FeatureLayer>::iterator it; for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) { if ( !( ++i % 100 ) ) emit progress( i ); if ( testCanceled() ) break; QgsGeometry g1 = it->feature.geometry(); QgsRectangle bb = g1.boundingBox(); QList<QgsFeatureId> crossingIds; crossingIds = index->intersects( bb ); QList<QgsFeatureId>::ConstIterator cit = crossingIds.begin(); QList<QgsFeatureId>::ConstIterator crossingIdsEnd = crossingIds.end(); for ( ; cit != crossingIdsEnd; ++cit ) { QgsFeature &f = mFeatureMap2[*cit].feature; QgsGeometry g2 = f.geometry(); // skip itself, when invoked with the same layer if ( skipItself && f.id() == it->feature.id() ) continue; if ( g2.isNull() ) { QgsMessageLog::logMessage( tr( "Second geometry missing." ), tr( "Topology plugin" ) ); continue; } if ( g1.overlaps( g2 ) ) { QgsRectangle r = bb; QgsRectangle r2 = g2.boundingBox(); r.combineExtentWith( r2 ); QgsGeometry conflictGeom = g1.intersection( g2 ); // could this for some reason return NULL? if ( conflictGeom.isNull() ) { continue; } if ( isExtent ) { if ( canvasExtentPoly.disjoint( conflictGeom ) ) { continue; } if ( canvasExtentPoly.crosses( conflictGeom ) ) { conflictGeom = conflictGeom.intersection( canvasExtentPoly ); } } //c = new QgsGeometry; QList<FeatureLayer> fls; FeatureLayer fl; fl.feature = f; fl.layer = layer2; fls << *it << fl; TopolErrorIntersection *err = new TopolErrorIntersection( r, conflictGeom, fls ); errorList << err; } } } return errorList; }