[processing] replace alg Eliminate with new alg EliminateSelection

* [processing] replace alg Eliminate with new alg EliminateSelection

* Deprecate Eliminate algorithm

* Expose new EliminateSelection algorithm in GUI

* Rename ouput layer

* Reflect algorithm changes in help

* Remove superfluous init method

* Simplify code, thanks Nyall

* Improve transfer of selection to processLayer

* Remove deprecated Eliminate algorithm

* Remove test for Eliminate

* Fix indentation
This commit is contained in:
Bernhard Ströbl 2017-02-23 09:33:28 +01:00 committed by Matthias Kuhn
parent f45c19cfab
commit 115ede60ce
9 changed files with 32 additions and 380 deletions

View File

@ -160,8 +160,9 @@ qgis:distancetonearesthub: >
qgis:dropgeometries: >
This algorithm removes any geometries from an input layer and returns a layer containing only the feature attributes.
qgis:eliminatesliverpolygons: >
This algorithm combines selected polygons of the input layer with certain adjacent polygons by erasing their common boundary. Eliminate can either use an existing selection or a logical query based on one of the layer's fields to make the selection itself. The adjacent polygon can be either the one with the largest or smallest area or the one sharing the largest common boundary with the polygon to be eliminated. Eliminate is normally used to get rid of sliver polygons, i.e. tiny polygons that are a result of polygon intersection processes where boundaries of the inputs are similar but not identical.
qgis:eliminateselectedpolygons: >
This algorithm combines selected polygons of the input layer with certain adjacent polygons by erasing their common boundary. The adjacent polygon can be either the one with the largest or smallest area or the one sharing the largest common boundary with the polygon to be eliminated. The selected features will always be eliminated whether the option "Use only selected features" is set or not.
Eliminate is normally used to get rid of sliver polygons, i.e. tiny polygons that are a result of polygon intersection processes where boundaries of the inputs are similar but not identical.
qgis:explodelines: >
This algorithm takes a lines layer and creates a new one in which each line is replaced is replaced by a set of lines representing the segments in the original line. Each line in the resulting layer contains only a start and an end point, with no intermediate nodes between them.

View File

@ -2,10 +2,10 @@
"""
***************************************************************************
Eliminate.py
EliminateSelection.py
---------------------
Date : August 2012
Copyright : (C) 2013 by Bernhard Ströbl
Date : January 2017
Copyright : (C) 2017 by Bernhard Ströbl
Email : bernhard.stroebl@jena.de
***************************************************************************
* *
@ -20,8 +20,8 @@ from builtins import str
from builtins import range
__author__ = 'Bernhard Ströbl'
__date__ = 'September 2013'
__copyright__ = '(C) 2013, Bernhard Ströbl'
__date__ = 'January 2017'
__copyright__ = '(C) 2017, Bernhard Ströbl'
# This will get replaced with a git SHA1 when you do a git archive
@ -30,7 +30,6 @@ __revision__ = '$Format:%H$'
import os
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtCore import QLocale, QDate, QVariant
from qgis.core import QgsFeatureRequest, QgsFeature, QgsGeometry
@ -38,9 +37,6 @@ from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.ProcessingLog import ProcessingLog
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterBoolean
from processing.core.parameters import ParameterTableField
from processing.core.parameters import ParameterString
from processing.core.parameters import ParameterSelection
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector
@ -48,15 +44,11 @@ from processing.tools import dataobjects, vector
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class Eliminate(GeoAlgorithm):
class EliminateSelection(GeoAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
MODE = 'MODE'
KEEPSELECTION = 'KEEPSELECTION'
ATTRIBUTE = 'ATTRIBUTE'
COMPARISONVALUE = 'COMPARISONVALUE'
COMPARISON = 'COMPARISON'
MODE_LARGEST_AREA = 0
MODE_SMALLEST_AREA = 1
@ -66,7 +58,7 @@ class Eliminate(GeoAlgorithm):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'eliminate.png'))
def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Eliminate sliver polygons')
self.name, self.i18n_name = self.trAlgorithm('Eliminate selected polygons')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
self.modes = [self.tr('Largest area'),
@ -75,153 +67,37 @@ class Eliminate(GeoAlgorithm):
self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(ParameterBoolean(self.KEEPSELECTION,
self.tr('Use current selection in input layer (works only if called from toolbox)'), False))
self.addParameter(ParameterTableField(self.ATTRIBUTE,
self.tr('Selection attribute'), self.INPUT))
self.comparisons = [
'==',
'!=',
'>',
'>=',
'<',
'<=',
'begins with',
'contains',
]
self.addParameter(ParameterSelection(self.COMPARISON,
self.tr('Comparison'), self.comparisons, default=0))
self.addParameter(ParameterString(self.COMPARISONVALUE,
self.tr('Value'), default='0'))
self.addParameter(ParameterSelection(self.MODE,
self.tr('Merge selection with the neighbouring polygon with the'),
self.modes))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Cleaned'), datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Eliminated'), datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
def processAlgorithm(self, feedback):
inLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))
boundary = self.getParameterValue(self.MODE) == self.MODE_BOUNDARY
smallestArea = self.getParameterValue(self.MODE) == self.MODE_SMALLEST_AREA
keepSelection = self.getParameterValue(self.KEEPSELECTION)
processLayer = vector.duplicateInMemory(inLayer)
if not keepSelection:
# Make a selection with the values provided
attribute = self.getParameterValue(self.ATTRIBUTE)
comparison = self.comparisons[self.getParameterValue(self.COMPARISON)]
comparisonvalue = self.getParameterValue(self.COMPARISONVALUE)
selectindex = vector.resolveFieldIndex(processLayer, attribute)
selectType = processLayer.fields()[selectindex].type()
selectionError = False
if selectType in [QVariant.Int, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong]:
try:
y = int(comparisonvalue)
except ValueError:
selectionError = True
msg = self.tr('Cannot convert "%s" to integer' % str(comparisonvalue))
elif selectType == QVariant.Double:
try:
y = float(comparisonvalue)
except ValueError:
selectionError = True
msg = self.tr('Cannot convert "%s" to float' % str(comparisonvalue))
elif selectType == QVariant.String:
# 10: string, boolean
try:
y = str(comparisonvalue)
except ValueError:
selectionError = True
msg = self.tr('Cannot convert "%s" to Unicode' % str(comparisonvalue))
elif selectType == QVariant.Date:
# date
dateAndFormat = comparisonvalue.split(' ')
if len(dateAndFormat) == 1:
# QDate object
y = QLocale.system().toDate(dateAndFormat[0])
if y.isNull():
msg = self.tr('Cannot convert "%s" to date with system date format %s' % (str(dateAndFormat), QLocale.system().dateFormat()))
elif len(dateAndFormat) == 2:
y = QDate.fromString(dateAndFormat[0], dateAndFormat[1])
if y.isNull():
msg = self.tr('Cannot convert "%s" to date with format string "%s"' % (str(dateAndFormat[0]), dateAndFormat[1]))
else:
y = QDate()
msg = ''
if y.isNull():
# Conversion was unsuccessful
selectionError = True
msg += self.tr('Enter the date and the date format, e.g. "07.26.2011" "MM.dd.yyyy".')
if (comparison == 'begins with' or comparison == 'contains') \
and selectType != QVariant.String:
selectionError = True
msg = self.tr('"%s" can only be used with string fields' % comparison)
selected = []
if selectionError:
raise GeoAlgorithmExecutionException(
self.tr('Error in selection input: %s' % msg))
else:
for feature in processLayer.getFeatures():
aValue = feature.attributes()[selectindex]
if aValue is None:
continue
if selectType in [QVariant.Int, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong]:
x = int(aValue)
elif selectType == QVariant.Double:
x = float(aValue)
elif selectType == QVariant.String:
# 10: string, boolean
x = str(aValue)
elif selectType == QVariant.Date:
# date
x = aValue # should be date
match = False
if comparison == '==':
match = x == y
elif comparison == '!=':
match = x != y
elif comparison == '>':
match = x > y
elif comparison == '>=':
match = x >= y
elif comparison == '<':
match = x < y
elif comparison == '<=':
match = x <= y
elif comparison == 'begins with':
match = x.startswith(y)
elif comparison == 'contains':
match = x.find(y) >= 0
if match:
selected.append(feature.id())
processLayer.selectByIds(selected)
if processLayer.selectedFeatureCount() == 0:
if inLayer.selectedFeatureCount() == 0:
ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
self.tr('%s: (No selection in input layer "%s")' % (self.commandLineName(), self.getParameterValue(self.INPUT))))
# Keep references to the features to eliminate
featToEliminate = []
for aFeat in processLayer.selectedFeatures():
featToEliminate.append(aFeat)
selFeatIds = inLayer.selectedFeatureIds()
output = self.getOutputFromName(self.OUTPUT)
writer = output.getVectorWriter(inLayer.fields(),
inLayer.wkbType(), inLayer.crs())
# Delete all features to eliminate in processLayer (we won't save this)
for aFeat in inLayer.getFeatures():
if aFeat.id() in selFeatIds:
# Keep references to the features to eliminate
featToEliminate.append(aFeat)
else:
# write the others to output
writer.addFeature(aFeat)
# Delete all features to eliminate in processLayer
processLayer = output.layer
processLayer.startEditing()
processLayer.deleteSelectedFeatures()
# ANALYZE
if len(featToEliminate) > 0: # Prevent zero division
@ -319,19 +195,8 @@ class Eliminate(GeoAlgorithm):
featToEliminate = featNotEliminated
# End while
# Create output
output = self.getOutputFromName(self.OUTPUT)
writer = output.getVectorWriter(processLayer.fields(),
processLayer.wkbType(), processLayer.crs())
# Write all features that are left over to output layer
iterator = processLayer.getFeatures()
for feature in iterator:
writer.addFeature(feature)
# Leave processLayer untouched
processLayer.rollBack()
if not processLayer.commitChanges():
raise GeoAlgorithmExecutionException(self.tr('Could not commit changes'))
for feature in featNotEliminated:
writer.addFeature(feature)

View File

@ -87,7 +87,6 @@ from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets
from .SelectByLocation import SelectByLocation
from .Union import Union
from .DensifyGeometriesInterval import DensifyGeometriesInterval
from .Eliminate import Eliminate
from .SpatialJoin import SpatialJoin
from .DeleteColumn import DeleteColumn
from .DeleteHoles import DeleteHoles
@ -187,6 +186,7 @@ from .FixGeometry import FixGeometry
from .ExecuteSQL import ExecuteSQL
from .FindProjection import FindProjection
from .TopoColors import TopoColor
from .EliminateSelection import EliminateSelection
pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@ -208,7 +208,7 @@ class QGISAlgorithmProvider(AlgorithmProvider):
DensifyGeometries(), DensifyGeometriesInterval(),
MultipartToSingleparts(), SinglePartsToMultiparts(),
PolygonsToLines(), LinesToPolygons(), ExtractNodes(),
Eliminate(), ConvexHull(), FixedDistanceBuffer(),
ConvexHull(), FixedDistanceBuffer(),
VariableDistanceBuffer(), Dissolve(), Difference(),
Intersection(), Union(), Clip(), ExtentFromLayer(),
RandomSelection(), RandomSelectionWithinSubsets(),
@ -257,7 +257,7 @@ class QGISAlgorithmProvider(AlgorithmProvider):
ShortestPathLayerToPoint(), ServiceAreaFromPoint(),
ServiceAreaFromLayer(), TruncateTable(), Polygonize(),
FixGeometry(), ExecuteSQL(), FindProjection(),
TopoColor()
TopoColor(), EliminateSelection()
]
if hasPlotly:

View File

@ -51,7 +51,7 @@ defaultMenuEntries.update({'qgis:convexhull': geoprocessingToolsMenu,
'qgis:clip': geoprocessingToolsMenu,
'qgis:difference': geoprocessingToolsMenu,
'qgis:dissolve': geoprocessingToolsMenu,
'qgis:eliminatesliverpolygons': geoprocessingToolsMenu})
'qgis:eliminateselectedpolygons': geoprocessingToolsMenu})
geometryToolsMenu = vectorMenu + "/" + Processing.tr('G&eometry Tools')
defaultMenuEntries.update({'qgis:checkvalidity': geometryToolsMenu,
'qgis:exportaddgeometrycolumns': geometryToolsMenu,

View File

@ -1,31 +0,0 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>eliminate_largest_area</Name>
<ElementPath>eliminate_largest_area</ElementPath>
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>5</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>10.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>6.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>5</Width>
</PropertyDefn>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:eliminate_largest_area fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 6,1 6,-3 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
</ogr:eliminate_largest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_largest_area fid="polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>Aaaaa</ogr:name>
<ogr:intval>-33</ogr:intval>
<ogr:floatval>0</ogr:floatval>
</ogr:eliminate_largest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_largest_area fid="polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,5 2,6 3,6 3,5 2,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>bbaaa</ogr:name>
<ogr:floatval>0.123</ogr:floatval>
</ogr:eliminate_largest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_largest_area fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
</ogr:eliminate_largest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_largest_area fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:eliminate_largest_area>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -1,31 +0,0 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>eliminate_smallest_area</Name>
<ElementPath>eliminate_smallest_area</ElementPath>
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>5</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>10.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>6.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>5</Width>
</PropertyDefn>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:eliminate_smallest_area fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
</ogr:eliminate_smallest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_smallest_area fid="polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>Aaaaa</ogr:name>
<ogr:intval>-33</ogr:intval>
<ogr:floatval>0</ogr:floatval>
</ogr:eliminate_smallest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_smallest_area fid="polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,5 2,6 3,6 3,5 2,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>bbaaa</ogr:name>
<ogr:floatval>0.123</ogr:floatval>
</ogr:eliminate_smallest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_smallest_area fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 2,-1 2,2 3,2 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
</ogr:eliminate_smallest_area>
</gml:featureMember>
<gml:featureMember>
<ogr:eliminate_smallest_area fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:eliminate_smallest_area>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -252,58 +252,6 @@ tests:
name: expected/autoincrement_field.gml
type: vector
# Eliminate sliver polygons
# case 1: merge with largest area
- algorithm: qgis:eliminatesliverpolygons
name: Eliminate sliver polygons largest area
params:
ATTRIBUTE: 'fid'
COMPARISON: '0'
COMPARISONVALUE: 'polys.5'
INPUT:
name: polys.gml
type: vector
KEEPSELECTION: 'False'
MODE: '0'
results:
OUTPUT:
name: expected/eliminate_largest_area.gml
type: vector
# case 2: merge with smallest area
- algorithm: qgis:eliminatesliverpolygons
name: Eliminate sliver polygons smallest area
params:
ATTRIBUTE: 'fid'
COMPARISON: '0'
COMPARISONVALUE: 'polys.5'
INPUT:
name: polys.gml
type: vector
KEEPSELECTION: 'False'
MODE: '1'
results:
OUTPUT:
name: expected/eliminate_smallest_area.gml
type: vector
# case 3: merge with longest common boundary
- algorithm: qgis:eliminatesliverpolygons
name: Eliminate sliver polygons largest area
params:
ATTRIBUTE: 'fid'
COMPARISON: '0'
COMPARISONVALUE: 'polys.5'
INPUT:
name: polys.gml
type: vector
KEEPSELECTION: 'False'
MODE: '2'
results:
OUTPUT:
name: expected/eliminate_largest_area.gml
type: vector
- algorithm: qgis:dissolve
name: Dissolve using field
params: