Skip to content

Commit

Permalink
Port Topocolor algorithm to new API
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent ec4df6c commit 7132faa
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 59 deletions.
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -133,6 +133,7 @@
from .SumLines import SumLines
from .SymmetricalDifference import SymmetricalDifference
from .TextToFloat import TextToFloat
from .TopoColors import TopoColor
from .Translate import Translate
from .TruncateTable import TruncateTable
from .Union import Union
Expand Down Expand Up @@ -169,7 +170,6 @@
# from .RasterCalculator import RasterCalculator
# from .ExecuteSQL import ExecuteSQL
# from .FindProjection import FindProjection
# from .TopoColors import TopoColor
# from .EliminateSelection import EliminateSelection

pluginPath = os.path.normpath(os.path.join(
Expand Down Expand Up @@ -206,7 +206,7 @@ def getAlgs(self):
# IdwInterpolation(), TinInterpolation(),
# RasterCalculator(),
# ExecuteSQL(), FindProjection(),
# TopoColor(), EliminateSelection()
# EliminateSelection()
# ]
algs = [AddTableField(),
Aspect(),
Expand Down Expand Up @@ -301,6 +301,7 @@ def getAlgs(self):
SumLines(),
SymmetricalDifference(),
TextToFloat(),
TopoColor(),
Translate(),
TruncateTable(),
Union(),
Expand Down
73 changes: 44 additions & 29 deletions python/plugins/processing/algs/qgis/TopoColors.py
Expand Up @@ -31,33 +31,31 @@

from collections import defaultdict

from qgis.core import (QgsApplication,
QgsField,
from qgis.core import (QgsField,
QgsFeatureSink,
QgsGeometry,
QgsSpatialIndex,
QgsPointXY,
NULL,
QgsProcessingUtils)
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterNumber,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink)

from qgis.PyQt.QtCore import (QVariant)

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import (ParameterVector,
ParameterSelection,
ParameterNumber)
from processing.core.outputs import OutputVector
from processing.tools import dataobjects

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]


class TopoColor(QgisAlgorithm):
INPUT_LAYER = 'INPUT_LAYER'
INPUT = 'INPUT'
MIN_COLORS = 'MIN_COLORS'
MIN_DISTANCE = 'MIN_DISTANCE'
BALANCE = 'BALANCE'
OUTPUT_LAYER = 'OUTPUT_LAYER'
OUTPUT = 'OUTPUT'

def tags(self):
return self.tr('topocolor,colors,graph,adjacent,assign').split(',')
Expand All @@ -69,21 +67,23 @@ def __init__(self):
super().__init__()

def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(ParameterNumber(self.MIN_COLORS,
self.tr('Minimum number of colors'), 1, 1000, 4))
self.addParameter(ParameterNumber(self.MIN_DISTANCE,
self.tr('Minimum distance between features'), 0.0, 999999999.0, 0.0))

self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon]))
self.addParameter(QgsProcessingParameterNumber(self.MIN_COLORS,
self.tr('Minimum number of colors'), minValue=1, maxValue=1000, defaultValue=4))
self.addParameter(QgsProcessingParameterNumber(self.MIN_DISTANCE,
self.tr('Minimum distance between features'), type=QgsProcessingParameterNumber.Double,
minValue=0.0, maxValue=999999999.0, defaultValue=0.0))
balance_by = [self.tr('By feature count'),
self.tr('By assigned area'),
self.tr('By distance between colors')]
self.addParameter(ParameterSelection(
self.addParameter(QgsProcessingParameterEnum(
self.BALANCE,
self.tr('Balance color assignment'),
balance_by, default=0))
options=balance_by, defaultValue=0))

self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Colored'), datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Colored'), QgsProcessing.TypeVectorPolygon))

def name(self):
return 'topologicalcoloring'
Expand All @@ -92,18 +92,18 @@ def displayName(self):
return self.tr('Topological coloring')

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
min_colors = self.getParameterValue(self.MIN_COLORS)
balance_by = self.getParameterValue(self.BALANCE)
min_distance = self.getParameterValue(self.MIN_DISTANCE)
source = self.parameterAsSource(parameters, self.INPUT, context)
min_colors = self.parameterAsInt(parameters, self.MIN_COLORS, context)
balance_by = self.parameterAsEnum(parameters, self.BALANCE, context)
min_distance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)

fields = layer.fields()
fields = source.fields()
fields.append(QgsField('color_id', QVariant.Int))

writer = self.getOutputFromName(
self.OUTPUT_LAYER).getVectorWriter(fields, layer.wkbType(), layer.crs(), context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, source.wkbType(), source.sourceCrs())

features = {f.id(): f for f in QgsProcessingUtils.getFeatures(layer, context)}
features = {f.id(): f for f in source.getFeatures()}

topology, id_graph = self.compute_graph(features, feedback, min_distance=min_distance)
feature_colors = ColoringAlgorithm.balanced(features,
Expand All @@ -118,6 +118,9 @@ def processAlgorithm(self, parameters, context, feedback):
total = 20.0 / len(features)
current = 0
for feature_id, input_feature in features.items():
if feedback.isCanceled():
break

output_feature = input_feature
attributes = input_feature.attributes()
if feature_id in feature_colors:
Expand All @@ -126,11 +129,11 @@ def processAlgorithm(self, parameters, context, feedback):
attributes.append(NULL)
output_feature.setAttributes(attributes)

writer.addFeature(output_feature, QgsFeatureSink.FastInsert)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
current += 1
feedback.setProgress(80 + int(current * total))

del writer
return {self.OUTPUT: dest_id}

@staticmethod
def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
Expand All @@ -148,6 +151,9 @@ def compute_graph(features, feedback, create_id_graph=False, min_distance=0):

i = 0
for feature_id, f in features_with_geometry.items():
if feedback.isCanceled():
break

g = f.geometry()
if min_distance > 0:
g = g.buffer(min_distance, 5)
Expand All @@ -172,6 +178,9 @@ def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
feedback.setProgress(int(i * total))

for feature_id, f in features_with_geometry.items():
if feedback.isCanceled():
break

if feature_id not in s.node_edge:
s.add_edge(feature_id, None)

Expand Down Expand Up @@ -206,6 +215,9 @@ def balanced(features, graph, feedback, balance=0, min_colors=4):
i = 0

for (feature_id, n) in sorted_by_count:
if feedback.isCanceled():
break

# first work out which already assigned colors are adjacent to this feature
adjacent_colors = set()
for neighbour in graph.node_edge[feature_id]:
Expand Down Expand Up @@ -240,6 +252,9 @@ def balanced(features, graph, feedback, balance=0, min_colors=4):
# loop through these, and calculate the minimum distance from this feature to the nearest
# feature with each assigned color
for other_feature_id, c in other_features.items():
if feedback.isCanceled():
break

other_geometry = features[other_feature_id].geometry()
other_centroid = QgsPointXY(other_geometry.centroid().geometry())

Expand Down
Expand Up @@ -41,7 +41,7 @@
<ogr:right>8.23935</ogr:right>
<ogr:bottom>-3.11331</ogr:bottom>
<ogr:id>11</ogr:id>
<ogr:color_id>4</ogr:color_id>
<ogr:color_id>5</ogr:color_id>
</ogr:topocolor_polys>
</gml:featureMember>
<gml:featureMember>
Expand All @@ -52,7 +52,7 @@
<ogr:right>8.23935</ogr:right>
<ogr:bottom>-6.11331</ogr:bottom>
<ogr:id>12</ogr:id>
<ogr:color_id>5</ogr:color_id>
<ogr:color_id>4</ogr:color_id>
</ogr:topocolor_polys>
</gml:featureMember>
<gml:featureMember>
Expand Down
53 changes: 27 additions & 26 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -2598,32 +2598,33 @@ tests:
name: expected/polygon_from_extent.gml
type: vector

# - algorithm: qgis:topologicalcoloring
# name: Topological coloring
# params:
# INPUT_LAYER:
# name: custom/adjacent_polys.gml
# type: vector
# MIN_COLORS: 4
# results:
# OUTPUT_LAYER:
# name: expected/topocolor_polys.gml
# type: vector
#
# - algorithm: qgis:topologicalcoloring
# name: Topological coloring w/ min distance
# params:
# BALANCE: '0'
# INPUT_LAYER:
# name: custom/adjacent_polys.gml
# type: vector
# MIN_COLORS: 4
# MIN_DISTANCE: 4.0
# results:
# OUTPUT_LAYER:
# name: expected/topocolor_polys_min_dist.gml
# type: vector
#
- algorithm: qgis:topologicalcoloring
name: Topological coloring
params:
BALANCE: 0
INPUT:
name: custom/adjacent_polys.gml
type: vector
MIN_COLORS: 4
results:
OUTPUT:
name: expected/topocolor_polys.gml
type: vector

- algorithm: qgis:topologicalcoloring
name: Topological coloring w/ min distance
params:
BALANCE: 0
INPUT:
name: custom/adjacent_polys.gml
type: vector
MIN_COLORS: 4
MIN_DISTANCE: 4.0
results:
OUTPUT:
name: expected/topocolor_polys_min_dist.gml
type: vector

- algorithm: qgis:regularpoints
name: Regular point with standard extent
params:
Expand Down

0 comments on commit 7132faa

Please sign in to comment.