mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			280 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
***************************************************************************
 | 
						|
    EliminateSelection.py
 | 
						|
    ---------------------
 | 
						|
    Date                 : January 2017
 | 
						|
    Copyright         : (C) 2017 by Bernhard Ströbl
 | 
						|
    Email                : bernhard.stroebl@jena.de
 | 
						|
***************************************************************************
 | 
						|
*                                                                         *
 | 
						|
*   This program is free software; you can redistribute it and/or modify  *
 | 
						|
*   it under the terms of the GNU General Public License as published by  *
 | 
						|
*   the Free Software Foundation; either version 2 of the License, or     *
 | 
						|
*   (at your option) any later version.                                   *
 | 
						|
*                                                                         *
 | 
						|
***************************************************************************
 | 
						|
"""
 | 
						|
 | 
						|
__author__ = "Bernhard Ströbl"
 | 
						|
__date__ = "January 2017"
 | 
						|
__copyright__ = "(C) 2017, Bernhard Ströbl"
 | 
						|
 | 
						|
import os
 | 
						|
 | 
						|
from qgis.PyQt.QtGui import QIcon
 | 
						|
 | 
						|
from qgis.core import (
 | 
						|
    QgsApplication,
 | 
						|
    QgsFeatureRequest,
 | 
						|
    QgsFeature,
 | 
						|
    QgsFeatureSink,
 | 
						|
    QgsGeometry,
 | 
						|
    QgsProcessingAlgorithm,
 | 
						|
    QgsProcessingException,
 | 
						|
    QgsProcessingUtils,
 | 
						|
    QgsProcessingParameterVectorLayer,
 | 
						|
    QgsProcessingParameterEnum,
 | 
						|
    QgsProcessing,
 | 
						|
    QgsProcessingParameterFeatureSink,
 | 
						|
)
 | 
						|
 | 
						|
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
 | 
						|
 | 
						|
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
 | 
						|
 | 
						|
 | 
						|
class EliminateSelection(QgisAlgorithm):
 | 
						|
    INPUT = "INPUT"
 | 
						|
    OUTPUT = "OUTPUT"
 | 
						|
    MODE = "MODE"
 | 
						|
 | 
						|
    MODE_LARGEST_AREA = 0
 | 
						|
    MODE_SMALLEST_AREA = 1
 | 
						|
    MODE_BOUNDARY = 2
 | 
						|
 | 
						|
    def icon(self):
 | 
						|
        return QgsApplication.getThemeIcon("/algorithms/mAlgorithmDissolve.svg")
 | 
						|
 | 
						|
    def svgIconPath(self):
 | 
						|
        return QgsApplication.iconPath("/algorithms/mAlgorithmDissolve.svg")
 | 
						|
 | 
						|
    def group(self):
 | 
						|
        return self.tr("Vector geometry")
 | 
						|
 | 
						|
    def groupId(self):
 | 
						|
        return "vectorgeometry"
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
    def flags(self):
 | 
						|
        return (
 | 
						|
            super().flags()
 | 
						|
            | QgsProcessingAlgorithm.Flag.FlagNoThreading
 | 
						|
            | QgsProcessingAlgorithm.Flag.FlagNotAvailableInStandaloneTool
 | 
						|
        )
 | 
						|
 | 
						|
    def initAlgorithm(self, config=None):
 | 
						|
        self.modes = [
 | 
						|
            self.tr("Largest Area"),
 | 
						|
            self.tr("Smallest Area"),
 | 
						|
            self.tr("Largest Common Boundary"),
 | 
						|
        ]
 | 
						|
 | 
						|
        self.addParameter(
 | 
						|
            QgsProcessingParameterVectorLayer(
 | 
						|
                self.INPUT,
 | 
						|
                self.tr("Input layer"),
 | 
						|
                [QgsProcessing.SourceType.TypeVectorPolygon],
 | 
						|
            )
 | 
						|
        )
 | 
						|
        self.addParameter(
 | 
						|
            QgsProcessingParameterEnum(
 | 
						|
                self.MODE,
 | 
						|
                self.tr("Merge selection with the neighbouring polygon with the"),
 | 
						|
                options=self.modes,
 | 
						|
            )
 | 
						|
        )
 | 
						|
 | 
						|
        self.addParameter(
 | 
						|
            QgsProcessingParameterFeatureSink(
 | 
						|
                self.OUTPUT,
 | 
						|
                self.tr("Eliminated"),
 | 
						|
                QgsProcessing.SourceType.TypeVectorPolygon,
 | 
						|
            )
 | 
						|
        )
 | 
						|
 | 
						|
    def name(self):
 | 
						|
        return "eliminateselectedpolygons"
 | 
						|
 | 
						|
    def displayName(self):
 | 
						|
        return self.tr("Eliminate selected polygons")
 | 
						|
 | 
						|
    def processAlgorithm(self, parameters, context, feedback):
 | 
						|
        inLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
 | 
						|
        boundary = (
 | 
						|
            self.parameterAsEnum(parameters, self.MODE, context) == self.MODE_BOUNDARY
 | 
						|
        )
 | 
						|
        smallestArea = (
 | 
						|
            self.parameterAsEnum(parameters, self.MODE, context)
 | 
						|
            == self.MODE_SMALLEST_AREA
 | 
						|
        )
 | 
						|
 | 
						|
        if inLayer.selectedFeatureCount() == 0:
 | 
						|
            feedback.reportError(
 | 
						|
                self.tr('{0}: (No selection in input layer "{1}")').format(
 | 
						|
                    self.displayName(), parameters[self.INPUT]
 | 
						|
                )
 | 
						|
            )
 | 
						|
 | 
						|
        featToEliminate = []
 | 
						|
        selFeatIds = inLayer.selectedFeatureIds()
 | 
						|
 | 
						|
        (sink, dest_id) = self.parameterAsSink(
 | 
						|
            parameters,
 | 
						|
            self.OUTPUT,
 | 
						|
            context,
 | 
						|
            inLayer.fields(),
 | 
						|
            inLayer.wkbType(),
 | 
						|
            inLayer.sourceCrs(),
 | 
						|
        )
 | 
						|
        if sink is None:
 | 
						|
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
 | 
						|
 | 
						|
        for aFeat in inLayer.getFeatures():
 | 
						|
            if feedback.isCanceled():
 | 
						|
                break
 | 
						|
 | 
						|
            if aFeat.id() in selFeatIds:
 | 
						|
                # Keep references to the features to eliminate
 | 
						|
                featToEliminate.append(aFeat)
 | 
						|
            else:
 | 
						|
                # write the others to output
 | 
						|
                sink.addFeature(aFeat, QgsFeatureSink.Flag.FastInsert)
 | 
						|
 | 
						|
        sink.finalize()
 | 
						|
        del sink
 | 
						|
 | 
						|
        # Delete all features to eliminate in processLayer
 | 
						|
        processLayer = QgsProcessingUtils.mapLayerFromString(dest_id, context)
 | 
						|
        processLayer.startEditing()
 | 
						|
 | 
						|
        # ANALYZE
 | 
						|
        if len(featToEliminate) > 0:  # Prevent zero division
 | 
						|
            start = 20.00
 | 
						|
            add = 80.00 / len(featToEliminate)
 | 
						|
        else:
 | 
						|
            start = 100
 | 
						|
 | 
						|
        feedback.setProgress(start)
 | 
						|
        madeProgress = True
 | 
						|
 | 
						|
        # We go through the list and see if we find any polygons we can
 | 
						|
        # merge the selected with. If we have no success with some we
 | 
						|
        # merge and then restart the whole story.
 | 
						|
        while madeProgress:  # Check if we made any progress
 | 
						|
            madeProgress = False
 | 
						|
            featNotEliminated = []
 | 
						|
 | 
						|
            # Iterate over the polygons to eliminate
 | 
						|
            for i in range(len(featToEliminate)):
 | 
						|
                if feedback.isCanceled():
 | 
						|
                    break
 | 
						|
 | 
						|
                feat = featToEliminate.pop()
 | 
						|
                geom2Eliminate = feat.geometry()
 | 
						|
                bbox = geom2Eliminate.boundingBox()
 | 
						|
                fit = processLayer.getFeatures(
 | 
						|
                    QgsFeatureRequest().setFilterRect(bbox).setSubsetOfAttributes([])
 | 
						|
                )
 | 
						|
                mergeWithFid = None
 | 
						|
                mergeWithGeom = None
 | 
						|
                max = 0
 | 
						|
                min = -1
 | 
						|
                selFeat = QgsFeature()
 | 
						|
 | 
						|
                # use prepared geometries for faster intersection tests
 | 
						|
                engine = QgsGeometry.createGeometryEngine(geom2Eliminate.constGet())
 | 
						|
                engine.prepareGeometry()
 | 
						|
 | 
						|
                while fit.nextFeature(selFeat):
 | 
						|
                    if feedback.isCanceled():
 | 
						|
                        break
 | 
						|
 | 
						|
                    selGeom = selFeat.geometry()
 | 
						|
 | 
						|
                    if engine.intersects(selGeom.constGet()):
 | 
						|
                        # We have a candidate
 | 
						|
                        iGeom = geom2Eliminate.intersection(selGeom)
 | 
						|
 | 
						|
                        if not iGeom:
 | 
						|
                            continue
 | 
						|
 | 
						|
                        if boundary:
 | 
						|
                            selValue = iGeom.length()
 | 
						|
                        else:
 | 
						|
                            # area. We need a common boundary in
 | 
						|
                            # order to merge
 | 
						|
                            if 0 < iGeom.length():
 | 
						|
                                selValue = selGeom.area()
 | 
						|
                            else:
 | 
						|
                                selValue = -1
 | 
						|
 | 
						|
                        if -1 != selValue:
 | 
						|
                            useThis = True
 | 
						|
 | 
						|
                            if smallestArea:
 | 
						|
                                if -1 == min:
 | 
						|
                                    min = selValue
 | 
						|
                                else:
 | 
						|
                                    if selValue < min:
 | 
						|
                                        min = selValue
 | 
						|
                                    else:
 | 
						|
                                        useThis = False
 | 
						|
                            else:
 | 
						|
                                if selValue > max:
 | 
						|
                                    max = selValue
 | 
						|
                                else:
 | 
						|
                                    useThis = False
 | 
						|
 | 
						|
                            if useThis:
 | 
						|
                                mergeWithFid = selFeat.id()
 | 
						|
                                mergeWithGeom = QgsGeometry(selGeom)
 | 
						|
                # End while fit
 | 
						|
 | 
						|
                if mergeWithFid is not None:
 | 
						|
                    # A successful candidate
 | 
						|
                    newGeom = mergeWithGeom.combine(geom2Eliminate)
 | 
						|
 | 
						|
                    if processLayer.changeGeometry(mergeWithFid, newGeom):
 | 
						|
                        madeProgress = True
 | 
						|
                    else:
 | 
						|
                        raise QgsProcessingException(
 | 
						|
                            self.tr(
 | 
						|
                                "Could not replace geometry of feature with id {0}"
 | 
						|
                            ).format(mergeWithFid)
 | 
						|
                        )
 | 
						|
 | 
						|
                    start = start + add
 | 
						|
                    feedback.setProgress(start)
 | 
						|
                else:
 | 
						|
                    featNotEliminated.append(feat)
 | 
						|
 | 
						|
            # End for featToEliminate
 | 
						|
 | 
						|
            featToEliminate = featNotEliminated
 | 
						|
 | 
						|
        # End while
 | 
						|
        if not processLayer.commitChanges():
 | 
						|
            raise QgsProcessingException(self.tr("Could not commit changes"))
 | 
						|
 | 
						|
        for feature in featNotEliminated:
 | 
						|
            if feedback.isCanceled():
 | 
						|
                break
 | 
						|
 | 
						|
            processLayer.dataProvider().addFeature(
 | 
						|
                feature, QgsFeatureSink.Flag.FastInsert
 | 
						|
            )
 | 
						|
 | 
						|
        return {self.OUTPUT: dest_id}
 |