Skip to content

Commit 74abd5b

Browse files
committed
[FEATURE][processing] Topological coloring can have a minimum
distance between features assigned the same color set
1 parent 5c43e0b commit 74abd5b

File tree

5 files changed

+206
-4
lines changed

5 files changed

+206
-4
lines changed

python/plugins/processing/algs/help/qgis.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ qgis:texttofloat: >
547547
qgis:topologicalcoloring: >
548548
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.
549549

550+
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.
551+
550552
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.
551553

552554
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.

python/plugins/processing/algs/qgis/TopoColors.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
class TopoColor(GeoAlgorithm):
5353
INPUT_LAYER = 'INPUT_LAYER'
5454
MIN_COLORS = 'MIN_COLORS'
55+
MIN_DISTANCE = 'MIN_DISTANCE'
5556
BALANCE = 'BALANCE'
5657
OUTPUT_LAYER = 'OUTPUT_LAYER'
5758

@@ -64,6 +65,8 @@ def defineCharacteristics(self):
6465
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON]))
6566
self.addParameter(ParameterNumber(self.MIN_COLORS,
6667
self.tr('Minimum number of colors'), 1, 1000, 4))
68+
self.addParameter(ParameterNumber(self.MIN_DISTANCE,
69+
self.tr('Minimum distance between features'), 0.0, 999999999.0, 0.0))
6770
balance_by = [self.tr('By feature count'),
6871
self.tr('By assigned area'),
6972
self.tr('By distance between colors')]
@@ -79,6 +82,7 @@ def processAlgorithm(self, feedback):
7982
self.getParameterValue(self.INPUT_LAYER))
8083
min_colors = self.getParameterValue(self.MIN_COLORS)
8184
balance_by = self.getParameterValue(self.BALANCE)
85+
min_distance = self.getParameterValue(self.MIN_DISTANCE)
8286

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

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

94-
topology, id_graph = self.compute_graph(features, feedback)
98+
topology, id_graph = self.compute_graph(features, feedback, min_distance=min_distance)
9599
feature_colors = ColoringAlgorithm.balanced(features,
96100
balance=balance_by,
97101
graph=topology,
@@ -119,7 +123,7 @@ def processAlgorithm(self, feedback):
119123
del writer
120124

121125
@staticmethod
122-
def compute_graph(features, feedback, create_id_graph=False):
126+
def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
123127
""" compute topology from a layer/field """
124128
s = Graph(sort_graph=False)
125129
id_graph = None
@@ -134,10 +138,14 @@ def compute_graph(features, feedback, create_id_graph=False):
134138

135139
i = 0
136140
for feature_id, f in features_with_geometry.items():
137-
engine = QgsGeometry.createGeometryEngine(f.geometry().geometry())
141+
g = f.geometry()
142+
if min_distance > 0:
143+
g = g.buffer(min_distance, 5)
144+
145+
engine = QgsGeometry.createGeometryEngine(g.geometry())
138146
engine.prepareGeometry()
139147

140-
feature_bounds = f.geometry().boundingBox()
148+
feature_bounds = g.boundingBox()
141149
# grow bounds a little so we get touching features
142150
feature_bounds.grow(feature_bounds.width() * 0.01)
143151
intersections = index.intersects(feature_bounds)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>topocolor_polys_min_dist</Name>
4+
<ElementPath>topocolor_polys_min_dist</ElementPath>
5+
<!--POLYGON-->
6+
<GeometryType>3</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>11</FeatureCount>
10+
<ExtentXMin>-0.76065</ExtentXMin>
11+
<ExtentXMax>14.23935</ExtentXMax>
12+
<ExtentYMin>-6.11331</ExtentYMin>
13+
<ExtentYMax>5.88669</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>left</Name>
17+
<ElementPath>left</ElementPath>
18+
<Type>Real</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>top</Name>
22+
<ElementPath>top</ElementPath>
23+
<Type>Real</Type>
24+
</PropertyDefn>
25+
<PropertyDefn>
26+
<Name>right</Name>
27+
<ElementPath>right</ElementPath>
28+
<Type>Real</Type>
29+
</PropertyDefn>
30+
<PropertyDefn>
31+
<Name>bottom</Name>
32+
<ElementPath>bottom</ElementPath>
33+
<Type>Real</Type>
34+
</PropertyDefn>
35+
<PropertyDefn>
36+
<Name>id</Name>
37+
<ElementPath>id</ElementPath>
38+
<Type>Integer</Type>
39+
</PropertyDefn>
40+
<PropertyDefn>
41+
<Name>color_id</Name>
42+
<ElementPath>color_id</ElementPath>
43+
<Type>Integer</Type>
44+
</PropertyDefn>
45+
</GMLFeatureClass>
46+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-0.760650357995228</gml:X><gml:Y>-6.11330548926014</gml:Y></gml:coord>
10+
<gml:coord><gml:X>14.2393496420048</gml:X><gml:Y>5.88669451073986</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.0">
16+
<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>
17+
<ogr:left>-0.76065</ogr:left>
18+
<ogr:top>-0.11331</ogr:top>
19+
<ogr:right>2.23935</ogr:right>
20+
<ogr:bottom>-3.11331</ogr:bottom>
21+
<ogr:id>3</ogr:id>
22+
<ogr:color_id>7</ogr:color_id>
23+
</ogr:topocolor_polys_min_dist>
24+
</gml:featureMember>
25+
<gml:featureMember>
26+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.1">
27+
<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>
28+
<ogr:left>-0.76065</ogr:left>
29+
<ogr:top>-3.11331</ogr:top>
30+
<ogr:right>2.23935</ogr:right>
31+
<ogr:bottom>-6.11331</ogr:bottom>
32+
<ogr:id>4</ogr:id>
33+
<ogr:color_id>2</ogr:color_id>
34+
</ogr:topocolor_polys_min_dist>
35+
</gml:featureMember>
36+
<gml:featureMember>
37+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.2">
38+
<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>
39+
<ogr:left>5.23935</ogr:left>
40+
<ogr:top>-0.11331</ogr:top>
41+
<ogr:right>8.23935</ogr:right>
42+
<ogr:bottom>-3.11331</ogr:bottom>
43+
<ogr:id>11</ogr:id>
44+
<ogr:color_id>1</ogr:color_id>
45+
</ogr:topocolor_polys_min_dist>
46+
</gml:featureMember>
47+
<gml:featureMember>
48+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.3">
49+
<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>
50+
<ogr:left>5.23935</ogr:left>
51+
<ogr:top>-3.11331</ogr:top>
52+
<ogr:right>8.23935</ogr:right>
53+
<ogr:bottom>-6.11331</ogr:bottom>
54+
<ogr:id>12</ogr:id>
55+
<ogr:color_id>3</ogr:color_id>
56+
</ogr:topocolor_polys_min_dist>
57+
</gml:featureMember>
58+
<gml:featureMember>
59+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.4">
60+
<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>
61+
<ogr:left>8.23935</ogr:left>
62+
<ogr:top>-3.11331</ogr:top>
63+
<ogr:right>11.23935</ogr:right>
64+
<ogr:bottom>-6.11331</ogr:bottom>
65+
<ogr:id>16</ogr:id>
66+
<ogr:color_id>5</ogr:color_id>
67+
</ogr:topocolor_polys_min_dist>
68+
</gml:featureMember>
69+
<gml:featureMember>
70+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.5">
71+
<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>
72+
<ogr:left>11.23935</ogr:left>
73+
<ogr:top>-0.11331</ogr:top>
74+
<ogr:right>14.23935</ogr:right>
75+
<ogr:bottom>-3.11331</ogr:bottom>
76+
<ogr:id>19</ogr:id>
77+
<ogr:color_id>4</ogr:color_id>
78+
</ogr:topocolor_polys_min_dist>
79+
</gml:featureMember>
80+
<gml:featureMember>
81+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.6">
82+
<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>
83+
<ogr:left>11.23935</ogr:left>
84+
<ogr:top>-3.11331</ogr:top>
85+
<ogr:right>14.23935</ogr:right>
86+
<ogr:bottom>-6.11331</ogr:bottom>
87+
<ogr:id>20</ogr:id>
88+
<ogr:color_id>7</ogr:color_id>
89+
</ogr:topocolor_polys_min_dist>
90+
</gml:featureMember>
91+
<gml:featureMember>
92+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.7">
93+
<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>
94+
<ogr:left>2.23935</ogr:left>
95+
<ogr:top>5.88669</ogr:top>
96+
<ogr:right>5.23935</ogr:right>
97+
<ogr:bottom>2.88669</ogr:bottom>
98+
<ogr:id>5</ogr:id>
99+
<ogr:color_id>3</ogr:color_id>
100+
</ogr:topocolor_polys_min_dist>
101+
</gml:featureMember>
102+
<gml:featureMember>
103+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.8">
104+
<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>
105+
<ogr:left>5.23935</ogr:left>
106+
<ogr:top>2.88669</ogr:top>
107+
<ogr:right>8.23935</ogr:right>
108+
<ogr:id>10</ogr:id>
109+
<ogr:color_id>2</ogr:color_id>
110+
</ogr:topocolor_polys_min_dist>
111+
</gml:featureMember>
112+
<gml:featureMember>
113+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.9">
114+
<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>
115+
<ogr:left>2.23935</ogr:left>
116+
<ogr:top>2.88669</ogr:top>
117+
<ogr:right>5.23935</ogr:right>
118+
<ogr:id>6</ogr:id>
119+
<ogr:color_id>4</ogr:color_id>
120+
</ogr:topocolor_polys_min_dist>
121+
</gml:featureMember>
122+
<gml:featureMember>
123+
<ogr:topocolor_polys_min_dist fid="adjacent_polys.10">
124+
<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>
125+
<ogr:left>5.23935</ogr:left>
126+
<ogr:top>5.88669</ogr:top>
127+
<ogr:right>8.23935</ogr:right>
128+
<ogr:bottom>2.88669</ogr:bottom>
129+
<ogr:id>9</ogr:id>
130+
<ogr:color_id>6</ogr:color_id>
131+
</ogr:topocolor_polys_min_dist>
132+
</gml:featureMember>
133+
</ogr:FeatureCollection>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -2385,3 +2385,16 @@ tests:
23852385
name: expected/topocolor_polys.gml
23862386
type: vector
23872387

2388+
- algorithm: qgis:topologicalcoloring
2389+
name: Topological coloring w/ min distance
2390+
params:
2391+
BALANCE: '0'
2392+
INPUT_LAYER:
2393+
name: custom/adjacent_polys.gml
2394+
type: vector
2395+
MIN_COLORS: 4
2396+
MIN_DISTANCE: 4.0
2397+
results:
2398+
OUTPUT_LAYER:
2399+
name: expected/topocolor_polys_min_dist.gml
2400+
type: vector

0 commit comments

Comments
 (0)