Branch data Line data Source code
1 : : /*************************************************************************** 2 : : qgsalgorithmclip.cpp 3 : : --------------------- 4 : : begin : April 2017 5 : : copyright : (C) 2017 by Nyall Dawson 6 : : email : nyall dot dawson at gmail dot com 7 : : ***************************************************************************/ 8 : : 9 : : /*************************************************************************** 10 : : * * 11 : : * This program is free software; you can redistribute it and/or modify * 12 : : * it under the terms of the GNU General Public License as published by * 13 : : * the Free Software Foundation; either version 2 of the License, or * 14 : : * (at your option) any later version. * 15 : : * * 16 : : ***************************************************************************/ 17 : : 18 : : #include "qgsalgorithmclip.h" 19 : : #include "qgsgeometryengine.h" 20 : : #include "qgsoverlayutils.h" 21 : : #include "qgsvectorlayer.h" 22 : : 23 : : ///@cond PRIVATE 24 : : 25 : 0 : QString QgsClipAlgorithm::name() const 26 : : { 27 : 0 : return QStringLiteral( "clip" ); 28 : : } 29 : : 30 : 0 : QgsProcessingAlgorithm::Flags QgsClipAlgorithm::flags() const 31 : : { 32 : 0 : Flags f = QgsProcessingAlgorithm::flags(); 33 : 0 : f |= QgsProcessingAlgorithm::FlagSupportsInPlaceEdits; 34 : 0 : return f; 35 : : } 36 : : 37 : 0 : QString QgsClipAlgorithm::displayName() const 38 : : { 39 : 0 : return QObject::tr( "Clip" ); 40 : : } 41 : : 42 : 0 : QStringList QgsClipAlgorithm::tags() const 43 : : { 44 : 0 : return QObject::tr( "clip,intersect,intersection,mask" ).split( ',' ); 45 : 0 : } 46 : : 47 : 0 : QString QgsClipAlgorithm::group() const 48 : : { 49 : 0 : return QObject::tr( "Vector overlay" ); 50 : : } 51 : : 52 : 0 : QString QgsClipAlgorithm::groupId() const 53 : : { 54 : 0 : return QStringLiteral( "vectoroverlay" ); 55 : : } 56 : : 57 : 0 : void QgsClipAlgorithm::initAlgorithm( const QVariantMap & ) 58 : : { 59 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); 60 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "OVERLAY" ), QObject::tr( "Overlay layer" ), QList< int >() << QgsProcessing::TypeVectorPolygon ) ); 61 : : 62 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Clipped" ) ) ); 63 : 0 : } 64 : : 65 : 0 : QString QgsClipAlgorithm::shortHelpString() const 66 : : { 67 : 0 : return QObject::tr( "This algorithm clips a vector layer using the features of an additional polygon layer. Only the parts of the features " 68 : : "in the Input layer that fall within the polygons of the Overlay layer will be added to the resulting layer." ) 69 : 0 : + QStringLiteral( "\n\n" ) 70 : 0 : + QObject::tr( "The attributes of the features are not modified, although properties such as area or length of the features will " 71 : : "be modified by the clipping operation. If such properties are stored as attributes, those attributes will have to " 72 : : "be manually updated." ); 73 : 0 : } 74 : : 75 : 0 : QgsClipAlgorithm *QgsClipAlgorithm::createInstance() const 76 : : { 77 : 0 : return new QgsClipAlgorithm(); 78 : : } 79 : : 80 : 0 : bool QgsClipAlgorithm::supportInPlaceEdit( const QgsMapLayer *l ) const 81 : : { 82 : 0 : const QgsVectorLayer *layer = qobject_cast< const QgsVectorLayer * >( l ); 83 : 0 : if ( !layer ) 84 : 0 : return false; 85 : : 86 : 0 : return layer->isSpatial(); 87 : 0 : } 88 : : 89 : 0 : QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) 90 : : { 91 : 0 : std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); 92 : 0 : if ( !featureSource ) 93 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); 94 : : 95 : 0 : std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); 96 : 0 : if ( !maskSource ) 97 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "OVERLAY" ) ) ); 98 : : 99 : 0 : if ( featureSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent ) 100 : 0 : feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) ); 101 : : 102 : 0 : QString dest; 103 : 0 : QgsWkbTypes::GeometryType sinkType = QgsWkbTypes::geometryType( featureSource->wkbType() ); 104 : 0 : std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs() ) ); 105 : : 106 : 0 : if ( !sink ) 107 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); 108 : : 109 : : // first build up a list of clip geometries 110 : 0 : QVector< QgsGeometry > clipGeoms; 111 : 0 : QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs(), context.transformContext() ) ); 112 : 0 : QgsFeature f; 113 : 0 : while ( it.nextFeature( f ) ) 114 : : { 115 : 0 : if ( f.hasGeometry() ) 116 : 0 : clipGeoms << f.geometry(); 117 : : } 118 : : 119 : 0 : QVariantMap outputs; 120 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), dest ); 121 : : 122 : 0 : if ( clipGeoms.isEmpty() ) 123 : 0 : return outputs; 124 : : 125 : : // are we clipping against a single feature? if so, we can show finer progress reports 126 : 0 : bool singleClipFeature = false; 127 : 0 : QgsGeometry combinedClipGeom; 128 : 0 : if ( clipGeoms.length() > 1 ) 129 : : { 130 : 0 : combinedClipGeom = QgsGeometry::unaryUnion( clipGeoms ); 131 : 0 : if ( combinedClipGeom.isEmpty() ) 132 : : { 133 : 0 : throw QgsProcessingException( QObject::tr( "Could not create the combined clip geometry: %1" ).arg( combinedClipGeom.lastError() ) ); 134 : : } 135 : 0 : singleClipFeature = false; 136 : 0 : } 137 : : else 138 : : { 139 : 0 : combinedClipGeom = clipGeoms.at( 0 ); 140 : 0 : singleClipFeature = true; 141 : : } 142 : : 143 : : // use prepared geometries for faster intersection tests 144 : 0 : std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( combinedClipGeom.constGet() ) ); 145 : 0 : engine->prepareGeometry(); 146 : : 147 : 0 : QgsFeatureIds testedFeatureIds; 148 : : 149 : 0 : int i = -1; 150 : 0 : const auto constClipGeoms = clipGeoms; 151 : 0 : for ( const QgsGeometry &clipGeom : constClipGeoms ) 152 : : { 153 : 0 : i++; 154 : 0 : if ( feedback->isCanceled() ) 155 : : { 156 : 0 : break; 157 : : } 158 : 0 : QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) ); 159 : 0 : QgsFeatureList inputFeatures; 160 : 0 : QgsFeature f; 161 : 0 : while ( inputIt.nextFeature( f ) ) 162 : 0 : inputFeatures << f; 163 : : 164 : 0 : if ( inputFeatures.isEmpty() ) 165 : 0 : continue; 166 : : 167 : 0 : double step = 0; 168 : 0 : if ( singleClipFeature ) 169 : 0 : step = 100.0 / inputFeatures.length(); 170 : : 171 : 0 : int current = 0; 172 : 0 : const auto constInputFeatures = inputFeatures; 173 : 0 : for ( const QgsFeature &inputFeature : constInputFeatures ) 174 : : { 175 : 0 : if ( feedback->isCanceled() ) 176 : : { 177 : 0 : break; 178 : : } 179 : : 180 : 0 : if ( !inputFeature.hasGeometry() ) 181 : 0 : continue; 182 : : 183 : 0 : if ( testedFeatureIds.contains( inputFeature.id() ) ) 184 : : { 185 : : // don't retest a feature we have already checked 186 : 0 : continue; 187 : : } 188 : 0 : testedFeatureIds.insert( inputFeature.id() ); 189 : : 190 : 0 : if ( !engine->intersects( inputFeature.geometry().constGet() ) ) 191 : 0 : continue; 192 : : 193 : 0 : QgsGeometry newGeometry; 194 : 0 : if ( !engine->contains( inputFeature.geometry().constGet() ) ) 195 : : { 196 : 0 : QgsGeometry currentGeometry = inputFeature.geometry(); 197 : 0 : newGeometry = combinedClipGeom.intersection( currentGeometry ); 198 : 0 : if ( newGeometry.wkbType() == QgsWkbTypes::Unknown || QgsWkbTypes::flatType( newGeometry.wkbType() ) == QgsWkbTypes::GeometryCollection ) 199 : : { 200 : 0 : QgsGeometry intCom = inputFeature.geometry().combine( newGeometry ); 201 : 0 : QgsGeometry intSym = inputFeature.geometry().symDifference( newGeometry ); 202 : 0 : newGeometry = intCom.difference( intSym ); 203 : 0 : } 204 : 0 : } 205 : : else 206 : : { 207 : : // clip geometry totally contains feature geometry, so no need to perform intersection 208 : 0 : newGeometry = inputFeature.geometry(); 209 : : } 210 : : 211 : 0 : if ( !QgsOverlayUtils::sanitizeIntersectionResult( newGeometry, sinkType ) ) 212 : 0 : continue; 213 : : 214 : 0 : QgsFeature outputFeature; 215 : 0 : outputFeature.setGeometry( newGeometry ); 216 : 0 : outputFeature.setAttributes( inputFeature.attributes() ); 217 : 0 : sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); 218 : : 219 : : 220 : 0 : if ( singleClipFeature ) 221 : 0 : feedback->setProgress( current * step ); 222 : 0 : } 223 : : 224 : 0 : if ( !singleClipFeature ) 225 : : { 226 : : // coarse progress report for multiple clip geometries 227 : 0 : feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() ); 228 : 0 : } 229 : 0 : } 230 : : 231 : 0 : return outputs; 232 : 0 : } 233 : : 234 : : ///@endcond