Skip to content

Commit

Permalink
[FEATURE][processing] Topological coloring can have a minimum
Browse files Browse the repository at this point in the history
distance between features assigned the same color set
  • Loading branch information
nyalldawson committed Feb 22, 2017
1 parent 5c43e0b commit 74abd5b
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 4 deletions.
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -547,6 +547,8 @@ qgis:texttofloat: >
qgis:topologicalcoloring: >
This algorithm assigns a color index to polygon features in such a way that no adjacent polygons share the same color index, whilst minimizing the number of colors required.

An optional minimum distance between features assigned the same color can be set to prevent nearby (but non-touching) features from being assigned equal colors.

The algorithm allows choice of method to use when assigning colors. The default method attempts to assign colors so that the count of features assigned to each individual color index is balanced.

The 'by assigned area' mode instead assigns colors so that the total area of features assigned to each color is balanced. This mode can be useful to help avoid large features resulting in one of the colors appearing more dominant on a colored map.
Expand Down
16 changes: 12 additions & 4 deletions python/plugins/processing/algs/qgis/TopoColors.py
Expand Up @@ -52,6 +52,7 @@
class TopoColor(GeoAlgorithm):
INPUT_LAYER = 'INPUT_LAYER'
MIN_COLORS = 'MIN_COLORS'
MIN_DISTANCE = 'MIN_DISTANCE'
BALANCE = 'BALANCE'
OUTPUT_LAYER = 'OUTPUT_LAYER'

Expand All @@ -64,6 +65,8 @@ def defineCharacteristics(self):
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))
balance_by = [self.tr('By feature count'),
self.tr('By assigned area'),
self.tr('By distance between colors')]
Expand All @@ -79,6 +82,7 @@ def processAlgorithm(self, feedback):
self.getParameterValue(self.INPUT_LAYER))
min_colors = self.getParameterValue(self.MIN_COLORS)
balance_by = self.getParameterValue(self.BALANCE)
min_distance = self.getParameterValue(self.MIN_DISTANCE)

fields = layer.fields()
fields.append(QgsField('color_id', QVariant.Int))
Expand All @@ -91,7 +95,7 @@ def processAlgorithm(self, feedback):

features = {f.id(): f for f in vector.features(layer)}

topology, id_graph = self.compute_graph(features, feedback)
topology, id_graph = self.compute_graph(features, feedback, min_distance=min_distance)
feature_colors = ColoringAlgorithm.balanced(features,
balance=balance_by,
graph=topology,
Expand Down Expand Up @@ -119,7 +123,7 @@ def processAlgorithm(self, feedback):
del writer

@staticmethod
def compute_graph(features, feedback, create_id_graph=False):
def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
""" compute topology from a layer/field """
s = Graph(sort_graph=False)
id_graph = None
Expand All @@ -134,10 +138,14 @@ def compute_graph(features, feedback, create_id_graph=False):

i = 0
for feature_id, f in features_with_geometry.items():
engine = QgsGeometry.createGeometryEngine(f.geometry().geometry())
g = f.geometry()
if min_distance > 0:
g = g.buffer(min_distance, 5)

engine = QgsGeometry.createGeometryEngine(g.geometry())
engine.prepareGeometry()

feature_bounds = f.geometry().boundingBox()
feature_bounds = g.boundingBox()
# grow bounds a little so we get touching features
feature_bounds.grow(feature_bounds.width() * 0.01)
intersections = index.intersects(feature_bounds)
Expand Down
@@ -0,0 +1,46 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>topocolor_polys_min_dist</Name>
<ElementPath>topocolor_polys_min_dist</ElementPath>
<!--POLYGON-->
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>11</FeatureCount>
<ExtentXMin>-0.76065</ExtentXMin>
<ExtentXMax>14.23935</ExtentXMax>
<ExtentYMin>-6.11331</ExtentYMin>
<ExtentYMax>5.88669</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>left</Name>
<ElementPath>left</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>top</Name>
<ElementPath>top</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>right</Name>
<ElementPath>right</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>bottom</Name>
<ElementPath>bottom</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>id</Name>
<ElementPath>id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>color_id</Name>
<ElementPath>color_id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,133 @@
<?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>-0.760650357995228</gml:X><gml:Y>-6.11330548926014</gml:Y></gml:coord>
<gml:coord><gml:X>14.2393496420048</gml:X><gml:Y>5.88669451073986</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-0.760650357995228,-0.113305489260142 2.23934964200477,-0.113305489260142 2.23934964200477,-3.11330548926014 -0.760650357995228,-3.11330548926014 -0.760650357995228,-0.113305489260142</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>-0.76065</ogr:left>
<ogr:top>-0.11331</ogr:top>
<ogr:right>2.23935</ogr:right>
<ogr:bottom>-3.11331</ogr:bottom>
<ogr:id>3</ogr:id>
<ogr:color_id>7</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-0.760650357995228,-3.11330548926014 2.23934964200477,-3.11330548926014 2.23934964200477,-6.11330548926014 -0.760650357995228,-6.11330548926014 -0.760650357995228,-3.11330548926014</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>-0.76065</ogr:left>
<ogr:top>-3.11331</ogr:top>
<ogr:right>2.23935</ogr:right>
<ogr:bottom>-6.11331</ogr:bottom>
<ogr:id>4</ogr:id>
<ogr:color_id>2</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.23934964200477,-0.113305489260142 8.23934964200477,-0.113305489260142 8.23934964200477,-3.11330548926014 5.23934964200477,-3.11330548926014 5.23934964200477,-0.113305489260142</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>5.23935</ogr:left>
<ogr:top>-0.11331</ogr:top>
<ogr:right>8.23935</ogr:right>
<ogr:bottom>-3.11331</ogr:bottom>
<ogr:id>11</ogr:id>
<ogr:color_id>1</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.23934964200477,-3.11330548926014 8.23934964200477,-3.11330548926014 8.23934964200477,-6.11330548926014 5.23934964200477,-6.11330548926014 5.23934964200477,-3.11330548926014</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>5.23935</ogr:left>
<ogr:top>-3.11331</ogr:top>
<ogr:right>8.23935</ogr:right>
<ogr:bottom>-6.11331</ogr:bottom>
<ogr:id>12</ogr:id>
<ogr:color_id>3</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.4">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8.23934964200477,-3.11330548926014 11.2393496420048,-3.11330548926014 11.2393496420048,-6.11330548926014 8.23934964200477,-6.11330548926014 8.23934964200477,-3.11330548926014</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>8.23935</ogr:left>
<ogr:top>-3.11331</ogr:top>
<ogr:right>11.23935</ogr:right>
<ogr:bottom>-6.11331</ogr:bottom>
<ogr:id>16</ogr:id>
<ogr:color_id>5</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>11.2393496420048,-0.113305489260142 14.2393496420048,-0.113305489260142 14.2393496420048,-3.11330548926014 11.2393496420048,-3.11330548926014 11.2393496420048,-0.113305489260142</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>11.23935</ogr:left>
<ogr:top>-0.11331</ogr:top>
<ogr:right>14.23935</ogr:right>
<ogr:bottom>-3.11331</ogr:bottom>
<ogr:id>19</ogr:id>
<ogr:color_id>4</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.6">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>11.2393496420048,-3.11330548926014 14.2393496420048,-3.11330548926014 14.2393496420048,-6.11330548926014 11.2393496420048,-6.11330548926014 11.2393496420048,-3.11330548926014</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>11.23935</ogr:left>
<ogr:top>-3.11331</ogr:top>
<ogr:right>14.23935</ogr:right>
<ogr:bottom>-6.11331</ogr:bottom>
<ogr:id>20</ogr:id>
<ogr:color_id>7</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.7">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2.23934964200477,5.88669451073986 5.23934964200477,5.88669451073986 5.23934964200477,2.88669451073986 2.23934964200477,2.88669451073986 2.23934964200477,-0.113305489260142 -0.760650357995228,-0.113305489260142 -0.760650357995228,2.88669451073986 -0.760650357995228,5.88669451073986 2.23934964200477,5.88669451073986</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>2.23935</ogr:left>
<ogr:top>5.88669</ogr:top>
<ogr:right>5.23935</ogr:right>
<ogr:bottom>2.88669</ogr:bottom>
<ogr:id>5</ogr:id>
<ogr:color_id>3</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.8">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.23934964200477,2.88669451073986 8.23934964200477,2.88669451073986 11.2393496420048,2.88669451073986 11.2393496420048,-0.113305489260142 11.2393496420048,-3.11330548926014 8.23934964200477,-3.11330548926014 8.23934964200477,-0.113305489260142 5.23934964200477,-0.113305489260142 5.23934964200477,2.88669451073986</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>5.23935</ogr:left>
<ogr:top>2.88669</ogr:top>
<ogr:right>8.23935</ogr:right>
<ogr:id>10</ogr:id>
<ogr:color_id>2</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.9">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2.23934964200477,2.88669451073986 5.23934964200477,2.88669451073986 5.23934964200477,-0.113305489260142 5.23934964200477,-3.11330548926014 5.23934964200477,-6.11330548926014 2.23934964200477,-6.11330548926014 2.23934964200477,-3.11330548926014 2.23934964200477,-0.113305489260142 2.23934964200477,2.88669451073986</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>2.23935</ogr:left>
<ogr:top>2.88669</ogr:top>
<ogr:right>5.23935</ogr:right>
<ogr:id>6</ogr:id>
<ogr:color_id>4</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
<gml:featureMember>
<ogr:topocolor_polys_min_dist fid="adjacent_polys.10">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.23934964200477,5.88669451073986 8.23934964200477,5.88669451073986 11.2393496420048,5.88669451073986 14.2393496420048,5.88669451073986 14.2393496420048,2.88669451073986 14.2393496420048,-0.113305489260142 11.2393496420048,-0.113305489260142 11.2393496420048,2.88669451073986 8.23934964200477,2.88669451073986 5.23934964200477,2.88669451073986 5.23934964200477,5.88669451073986</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:left>5.23935</ogr:left>
<ogr:top>5.88669</ogr:top>
<ogr:right>8.23935</ogr:right>
<ogr:bottom>2.88669</ogr:bottom>
<ogr:id>9</ogr:id>
<ogr:color_id>6</ogr:color_id>
</ogr:topocolor_polys_min_dist>
</gml:featureMember>
</ogr:FeatureCollection>
13 changes: 13 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -2385,3 +2385,16 @@ tests:
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

0 comments on commit 74abd5b

Please sign in to comment.